diff options
author | Luboš Luňák <l.lunak@collabora.com> | 2020-06-30 10:19:34 +0200 |
---|---|---|
committer | Luboš Luňák <l.lunak@collabora.com> | 2020-07-01 07:36:39 +0200 |
commit | d1fcc9053f98cc4afb52498b40a836884bb5ec6f (patch) | |
tree | 1bc3fed7ffc3972ad051f1a46f759825eeb82690 /vcl | |
parent | 8663d81828541072999b26451f7d6e6bfcb5f951 (diff) |
optimize Bitmap::Erase() for Skia by delaying the erase (tdf#134363)
Tdf#134363 causes OutputDevice::DrawTransformBitmapExDirect()
to create a huge 1bpp bitmap as mask, and Skia code then tries
to convert all the bits to a format Skia would understand. Which
is wasteful, as SkShader with the color given will do the same
task much more efficiently.
Change-Id: If0bba16f56fed9c3720be801b25a1e703054ed8d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/97488
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/inc/salbmp.hxx | 5 | ||||
-rw-r--r-- | vcl/inc/skia/gdiimpl.hxx | 6 | ||||
-rw-r--r-- | vcl/inc/skia/salbmp.hxx | 15 | ||||
-rw-r--r-- | vcl/skia/gdiimpl.cxx | 87 | ||||
-rw-r--r-- | vcl/skia/salbmp.cxx | 134 | ||||
-rw-r--r-- | vcl/source/bitmap/bitmappaint.cxx | 13 |
6 files changed, 231 insertions, 29 deletions
diff --git a/vcl/inc/salbmp.hxx b/vcl/inc/salbmp.hxx index 4244ae1a376f..ab16b317d7eb 100644 --- a/vcl/inc/salbmp.hxx +++ b/vcl/inc/salbmp.hxx @@ -86,6 +86,11 @@ public: return false; } + virtual bool Erase( const Color& /*color*/ ) + { + return false; + } + void GetChecksum(BitmapChecksum& rChecksum) const { updateChecksum(); diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx index 73fbee394072..c98038807287 100644 --- a/vcl/inc/skia/gdiimpl.hxx +++ b/vcl/inc/skia/gdiimpl.hxx @@ -200,10 +200,14 @@ public: #endif // Default blend mode for SkPaint is SkBlendMode::kSrcOver + void drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap, + SkBlendMode blendMode = SkBlendMode::kSrcOver); + void drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage, SkBlendMode eBlendMode = SkBlendMode::kSrcOver); - void drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader); + void drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader, + SkBlendMode blendMode = SkBlendMode::kSrcOver); enum class GlyphOrientation { diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx index d5491e367700..4ee6e4908ee7 100644 --- a/vcl/inc/skia/salbmp.hxx +++ b/vcl/inc/skia/salbmp.hxx @@ -59,13 +59,24 @@ public: sal_uInt8 nTol) override; virtual bool InterpretAs8Bit() override; virtual bool ConvertToGreyscale() override; + virtual bool Erase(const Color& color) override; const BitmapPalette& Palette() const { return mPalette; } + + // True if GetSkShader() should be preferred to GetSkImage() (or the Alpha variants). + bool PreferSkShader() const; + // Returns the contents as SkImage (possibly GPU-backed). const sk_sp<SkImage>& GetSkImage() const; + sk_sp<SkShader> GetSkShader() const; // Returns the contents as alpha SkImage (possibly GPU-backed) const sk_sp<SkImage>& GetAlphaSkImage() const; + sk_sp<SkShader> GetAlphaSkShader() const; + + // Key for caching/hashing. + OString GetImageKey() const; + OString GetAlphaImageKey() const; #ifdef DBG_UTIL void dump(const char* file) const; @@ -86,6 +97,7 @@ private: void EnsureBitmapUniqueData(); // Allocate mBuffer (with uninitialized contents). bool CreateBitmapData(); + void EraseInternal(); SkBitmap GetAsSkBitmap() const; #ifdef DBG_UTIL void verify() const; @@ -126,6 +138,9 @@ private: // data in mBuffer, if it differs from mSize, then there is a scaling operation pending. Size mPixelsSize; SkFilterQuality mScaleQuality = kHigh_SkFilterQuality; // quality for on-demand scaling + // Erase() is delayed, just sets these two instead of filling the buffer. + bool mEraseColorSet = false; + Color mEraseColor; #ifdef DBG_UTIL int mWriteAccessCount = 0; // number of write AcquireAccess() that have not been released #endif diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx index b00b74d24647..c11705486926 100644 --- a/vcl/skia/gdiimpl.cxx +++ b/vcl/skia/gdiimpl.cxx @@ -1009,7 +1009,7 @@ 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(). - drawImage(rPosAry, rSkiaBitmap.GetSkImage(), SkBlendMode::kMultiply); + drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply); return true; } @@ -1040,11 +1040,11 @@ bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry, // First do the "( 1 - alpha ) * mask" // (no idea how to do "floor", but hopefully not needed in practice). sk_sp<SkShader> shaderAlpha - = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkImage()->makeShader(), - rSkiaAlphaBitmap.GetAlphaSkImage()->makeShader()); + = SkShaders::Blend(SkBlendMode::kDstOut, rSkiaMaskBitmap.GetAlphaSkShader(), + rSkiaAlphaBitmap.GetAlphaSkShader()); // And now draw the bitmap with "1 - x", where x is the "( 1 - alpha ) * mask". - sk_sp<SkShader> shader = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha, - rSkiaSourceBitmap.GetSkImage()->makeShader()); + sk_sp<SkShader> shader + = SkShaders::Blend(SkBlendMode::kSrcOut, shaderAlpha, rSkiaSourceBitmap.GetSkShader()); drawShader(rPosAry, shader); return true; } @@ -1057,7 +1057,7 @@ void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& assert(dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap)); const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap); - drawImage(rPosAry, rSkiaSourceBitmap.GetSkImage()); + drawBitmap(rPosAry, rSkiaSourceBitmap); } void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, @@ -1074,7 +1074,7 @@ void SkiaSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& r drawShader(rPosAry, SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha. SkShaders::Color(toSkColor(nMaskColor)), - skiaBitmap.GetAlphaSkImage()->makeShader())); + skiaBitmap.GetAlphaSkShader())); } std::shared_ptr<SalBitmap> SkiaSalGraphicsImpl::getBitmap(long nX, long nY, long nWidth, @@ -1227,6 +1227,31 @@ void SkiaSalGraphicsImpl::invert(sal_uInt32 nPoints, const SalPoint* pPointArray bool SkiaSalGraphicsImpl::drawEPS(long, long, long, long, void*, sal_uInt32) { return false; } +static void drawBitmapToCanvas(const SkiaSalBitmap& bitmap, SkCanvas* canvas, const SkPaint& paint) +{ + if (bitmap.PreferSkShader()) + { + SkPaint paint2(paint); + paint2.setShader(bitmap.GetSkShader()); + canvas->drawPaint(paint2); + } + else + canvas->drawImage(bitmap.GetSkImage(), 0, 0, &paint); +} + +static void drawAlphaBitmapToCanvas(const SkiaSalBitmap& bitmap, SkCanvas* canvas, + const SkPaint& paint) +{ + if (bitmap.PreferSkShader()) + { + SkPaint paint2(paint); + paint2.setShader(bitmap.GetAlphaSkShader()); + canvas->drawPaint(paint2); + } + else + canvas->drawImage(bitmap.GetAlphaSkImage(), 0, 0, &paint); +} + // Create SkImage from a bitmap and possibly an alpha mask (the usual VCL one-minus-alpha), // with the given target size. Result will be possibly cached, unless disabled. sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitmap, @@ -1254,15 +1279,10 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma keyBuf.append(targetSize.Width()) .append("x") .append(targetSize.Height()) - .append("_0x") - .append(reinterpret_cast<sal_IntPtr>(&bitmap), 16) - .append("_0x") - .append(reinterpret_cast<sal_IntPtr>(alphaBitmap), 16) .append("_") - .append(static_cast<sal_Int64>(bitmap.GetSkImage()->uniqueID())); + .append(bitmap.GetImageKey()); if (alphaBitmap) - keyBuf.append("_").append( - static_cast<sal_Int64>(alphaBitmap->GetAlphaSkImage()->uniqueID())); + keyBuf.append("_").append(alphaBitmap->GetAlphaImageKey()); key = keyBuf.makeStringAndClear(); image = SkiaHelper::findCachedImage(key); if (image) @@ -1279,9 +1299,9 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma return nullptr; SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha - mergedSurface->getCanvas()->drawImage(bitmap.GetSkImage(), 0, 0, &paint); + drawBitmapToCanvas(bitmap, mergedSurface->getCanvas(), paint); paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha - mergedSurface->getCanvas()->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint); + drawAlphaBitmapToCanvas(*alphaBitmap, mergedSurface->getCanvas(), paint); sk_sp<SkSurface> scaledSurface = SkiaHelper::createSkSurface(targetSize); if (!scaledSurface) return nullptr; @@ -1310,11 +1330,11 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma paint.setFilterQuality(kHigh_SkFilterQuality); } paint.setBlendMode(SkBlendMode::kSrc); // copy as is, including alpha - canvas->drawImage(bitmap.GetSkImage(), 0, 0, &paint); + drawBitmapToCanvas(bitmap, canvas, paint); if (alphaBitmap != nullptr) { paint.setBlendMode(SkBlendMode::kDstOut); // VCL alpha is one-minus-alpha - canvas->drawImage(alphaBitmap->GetAlphaSkImage(), 0, 0, &paint); + drawAlphaBitmapToCanvas(*alphaBitmap, canvas, paint); } image = tmpSurface->makeImageSnapshot(); } @@ -1349,13 +1369,21 @@ bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBi else drawShader( rPosAry, - SkShaders::Blend( - SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha. - static_cast<const SkiaSalBitmap&>(rSourceBitmap).GetSkImage()->makeShader(), - static_cast<const SkiaSalBitmap*>(&rAlphaBitmap)->GetAlphaSkImage()->makeShader())); + SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha. + static_cast<const SkiaSalBitmap&>(rSourceBitmap).GetSkShader(), + static_cast<const SkiaSalBitmap*>(&rAlphaBitmap)->GetAlphaSkShader())); return true; } +void SkiaSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SkiaSalBitmap& bitmap, + SkBlendMode blendMode) +{ + if (bitmap.PreferSkShader()) + drawShader(rPosAry, bitmap.GetSkShader(), blendMode); + else + drawImage(rPosAry, bitmap.GetSkImage(), blendMode); +} + void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkImage>& aImage, SkBlendMode eBlendMode) { @@ -1379,13 +1407,15 @@ void SkiaSalGraphicsImpl::drawImage(const SalTwoRect& rPosAry, const sk_sp<SkIma // SkShader can be used to merge multiple bitmaps with appropriate blend modes (e.g. when // merging a bitmap with its alpha mask). -void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader) +void SkiaSalGraphicsImpl::drawShader(const SalTwoRect& rPosAry, const sk_sp<SkShader>& shader, + SkBlendMode blendMode) { preDraw(); SAL_INFO("vcl.skia.trace", "drawshader(" << this << "): " << rPosAry); SkRect destinationRect = SkRect::MakeXYWH(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight); SkPaint paint; + paint.setBlendMode(blendMode); paint.setShader(shader); if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight) paint.setFilterQuality(kHigh_SkFilterQuality); @@ -1472,8 +1502,15 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull, // SkCanvas::drawPaint() cannot do rectangles, so clip (is transformed by the matrix too). canvas->clipRect(SkRect::MakeWH(aSize.Width(), aSize.Height())); paint.setShader(SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha. - rSkiaBitmap.GetSkImage()->makeShader(), - pSkiaAlphaBitmap->GetAlphaSkImage()->makeShader())); + rSkiaBitmap.GetSkShader(), + pSkiaAlphaBitmap->GetAlphaSkShader())); + canvas->drawPaint(paint); + } + else if (rSkiaBitmap.PreferSkShader()) + { + // SkCanvas::drawPaint() cannot do rectangles, so clip (is transformed by the matrix too). + canvas->clipRect(SkRect::MakeWH(aSize.Width(), aSize.Height())); + paint.setShader(rSkiaBitmap.GetSkShader()); canvas->drawPaint(paint); } else diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx index 35d0c2a0849c..43286ac4a9fe 100644 --- a/vcl/skia/salbmp.cxx +++ b/vcl/skia/salbmp.cxx @@ -27,6 +27,8 @@ #include <salinst.hxx> #include <scanlinewriter.hxx> #include <svdata.hxx> +#include <bmpfast.hxx> +#include <vcl/bitmapaccess.hxx> #include <SkCanvas.h> #include <SkImage.h> @@ -148,6 +150,8 @@ bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount) mPixelsSize = src.mPixelsSize; mScanlineSize = src.mScanlineSize; mScaleQuality = src.mScaleQuality; + mEraseColorSet = src.mEraseColorSet; + mEraseColor = src.mEraseColor; #ifdef DBG_UTIL mWriteAccessCount = 0; #endif @@ -400,6 +404,17 @@ bool SkiaSalBitmap::InterpretAs8Bit() return false; } +bool SkiaSalBitmap::Erase(const Color& color) +{ + // Optimized variant, just remember the color and apply it when needed, + // which may save having to do format conversions (e.g. GetSkImage() + // may directly erase the SkImage). + ResetCachedData(); + mEraseColorSet = true; + mEraseColor = color; + return true; +} + SkBitmap SkiaSalBitmap::GetAsSkBitmap() const { #ifdef DBG_UTIL @@ -496,11 +511,28 @@ SkBitmap SkiaSalBitmap::GetAsSkBitmap() const return bitmap; } +static SkColor toSkColor(Color color) +{ + return SkColorSetARGB(255 - color.GetTransparency(), color.GetRed(), color.GetGreen(), + color.GetBlue()); +} + const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage() const { #ifdef DBG_UTIL assert(mWriteAccessCount == 0); #endif + if (mEraseColorSet) + { + SkiaZone zone; + sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mSize); + assert(surface); + surface->getCanvas()->clear(toSkColor(mEraseColor)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mImage = surface->makeImageSnapshot(); + SAL_INFO("vcl.skia.trace", "getskimage(" << this << ") from erase color " << mEraseColor); + return mImage; + } if (mPixelsSize != mSize && !mImage && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster) { @@ -554,6 +586,18 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const #ifdef DBG_UTIL assert(mWriteAccessCount == 0); #endif + if (mEraseColorSet) + { + SkiaZone zone; + sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(mSize, kAlpha_8_SkColorType); + assert(surface); + surface->getCanvas()->clear(SkColorSetARGB(255 - mEraseColor.GetBlue(), 0, 0, 0)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mAlphaImage = surface->makeImageSnapshot(); + SAL_INFO("vcl.skia.trace", + "getalphaskimage(" << this << ") from erase color " << mEraseColor); + return mAlphaImage; + } if (mAlphaImage) { assert(mSize == mPixelsSize); // data has already been scaled if needed @@ -636,8 +680,71 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const return mAlphaImage; } +// If the bitmap is to be erased, SkShader with the color set is more efficient +// than creating an image filled with the color. +bool SkiaSalBitmap::PreferSkShader() const { return mEraseColorSet; } + +sk_sp<SkShader> SkiaSalBitmap::GetSkShader() const +{ + if (mEraseColorSet) + return SkShaders::Color(toSkColor(mEraseColor)); + return GetSkImage()->makeShader(); +} + +sk_sp<SkShader> SkiaSalBitmap::GetAlphaSkShader() const +{ + if (mEraseColorSet) + return SkShaders::Color(toSkColor(mEraseColor)); + return GetAlphaSkImage()->makeShader(); +} + +void SkiaSalBitmap::EraseInternal() +{ + if (mPixelsSize.IsEmpty()) + return; + BitmapBuffer* bitmapBuffer = AcquireBuffer(BitmapAccessMode::Write); + if (bitmapBuffer == nullptr) + abort(); + Color fastColor = mEraseColor; + if (!!mPalette) + fastColor = mPalette.GetBestIndex(fastColor); + if (!ImplFastEraseBitmap(*bitmapBuffer, fastColor)) + { + FncSetPixel setPixel = BitmapReadAccess::SetPixelFunction(bitmapBuffer->mnFormat); + assert(bitmapBuffer->mnFormat & ScanlineFormat::TopDown); + // Set first scanline, copy to others. + Scanline scanline = bitmapBuffer->mpBits; + for (long x = 0; x < bitmapBuffer->mnWidth; ++x) + setPixel(scanline, x, mEraseColor, bitmapBuffer->maColorMask); + for (long y = 1; y < bitmapBuffer->mnHeight; ++y) + memcpy(scanline + y * bitmapBuffer->mnScanlineSize, scanline, + bitmapBuffer->mnScanlineSize); + } + ReleaseBuffer(bitmapBuffer, BitmapAccessMode::Write); +} + void SkiaSalBitmap::EnsureBitmapData() { + if (mEraseColorSet) + { + SkiaZone zone; + if (mPixelsSize != mSize) + { + mPixelsSize = mSize; + mBuffer.reset(); + } + mScaleQuality = kHigh_SkFilterQuality; + if (!mBuffer && !CreateBitmapData()) + abort(); + // Unset now, so that repeated call will return mBuffer. + mEraseColorSet = false; + EraseInternal(); + verify(); + SAL_INFO("vcl.skia.trace", + "ensurebitmapdata(" << this << ") from erase color " << mEraseColor); + return; + } + if (mBuffer) { if (mSize == mPixelsSize) @@ -768,9 +875,6 @@ void SkiaSalBitmap::EnsureBitmapUniqueData() void SkiaSalBitmap::ResetCachedData() { SkiaZone zone; - // There may be a case when only mImage is set and CreatBitmapData() will create - // mBuffer from it if needed, in that case ResetToSkImage() should be used. - assert(mBuffer.get() || !mImage); mImage.reset(); mAlphaImage.reset(); } @@ -794,6 +898,30 @@ void SkiaSalBitmap::ResetCachedDataBySize() mAlphaImage.reset(); } +OString SkiaSalBitmap::GetImageKey() const +{ + if (mEraseColorSet) + { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(2) << (255 - mEraseColor.GetTransparency()) + << std::setw(6) << sal_uInt32(mEraseColor.GetRGBColor()); + return OStringLiteral("E") + ss.str().c_str(); + } + return OStringLiteral("I") + OString::number(GetSkImage()->uniqueID()); +} + +OString SkiaSalBitmap::GetAlphaImageKey() const +{ + if (mEraseColorSet) + { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(2) << (255 - mEraseColor.GetTransparency()) + << std::setw(6) << sal_uInt32(mEraseColor.GetRGBColor()); + return OStringLiteral("E") + ss.str().c_str(); + } + return OStringLiteral("I") + OString::number(GetAlphaSkImage()->uniqueID()); +} + #ifdef DBG_UTIL void SkiaSalBitmap::dump(const char* file) const { diff --git a/vcl/source/bitmap/bitmappaint.cxx b/vcl/source/bitmap/bitmappaint.cxx index 22ddf2ea5e6b..046b0e3ba3b5 100644 --- a/vcl/source/bitmap/bitmappaint.cxx +++ b/vcl/source/bitmap/bitmappaint.cxx @@ -36,6 +36,19 @@ bool Bitmap::Erase(const Color& rFillColor) if (IsEmpty()) return true; + if (mxSalBmp) + { + // implementation specific replace + std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap()); + if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Erase(rFillColor)) + { + ImplSetSalBitmap(xImpBmp); + maPrefMapMode = MapMode(MapUnit::MapPixel); + maPrefSize = xImpBmp->GetSize(); + return true; + } + } + BitmapScopedWriteAccess pWriteAcc(*this); bool bRet = false; |