diff options
-rw-r--r-- | drawinglayer/source/primitive2d/glowprimitive2d.cxx | 24 | ||||
-rw-r--r-- | drawinglayer/source/primitive2d/shadowprimitive2d.cxx | 22 | ||||
-rw-r--r-- | drawinglayer/source/processor2d/vclhelperbufferdevice.cxx | 218 | ||||
-rw-r--r-- | drawinglayer/source/processor2d/vclhelperbufferdevice.hxx | 75 | ||||
-rw-r--r-- | drawinglayer/source/tools/converters.cxx | 127 | ||||
-rw-r--r-- | include/drawinglayer/converters.hxx | 12 | ||||
-rw-r--r-- | solenv/clang-format/excludelist | 2 |
7 files changed, 395 insertions, 85 deletions
diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx index 44a97c536fb2..956a6f94eb82 100644 --- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -155,24 +155,22 @@ void GlowPrimitive2D::create2DDecomposition( // limitation to be safe and not go runtime/memory havoc. Use a pretty small // limit due to this is glow functionality and will look good with bitmap scaling // anyways. The value of 250.000 square pixels below maybe adapted as needed. - // NOTE: This may be further optimized. Only the alpha channel is needed, so - // convertToBitmapEx may be split in tooling to have a version that only - // creates the alpha channel. Potential win is >50% for the alpha pixel - // creation step ('>' because alpha painting uses a ColorStack and thus - // often can used simplified rendering) const basegfx::B2DVector aDiscreteClippedSize(rViewInformation.getObjectToViewTransformation() * aClippedRange.getRange()); const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); const geometry::ViewInformation2D aViewInformation2D; const sal_uInt32 nMaximumQuadraticPixels(250000); - const BitmapEx aBitmapEx(::drawinglayer::convertToBitmapEx( + + // I have now added a helper that just creates the mask without having + // to render the content, use it, it's faster + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, nMaximumQuadraticPixels)); - if (!aBitmapEx.IsEmpty()) + if (!aAlpha.IsEmpty()) { - const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel()); + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); if (rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0) { @@ -183,7 +181,7 @@ void GlowPrimitive2D::create2DDecomposition( if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) { - // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx), + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), // so adapt numerically to a single scale value, they are integer rounded values const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) / static_cast<double>(nDiscreteClippedWidth)); @@ -198,12 +196,12 @@ void GlowPrimitive2D::create2DDecomposition( // When blurring a sharp boundary (our case), it gets 50% of original intensity, and // fades to both sides by the blur radius; thus blur radius is half of glow radius. // Consider glow transparency (initial transparency near the object edge) - const AlphaMask mask(ProcessAndBlurAlphaMask( - aBitmapEx.GetAlpha(), fDiscreteGlowRadius * fScale / 2.0, - fDiscreteGlowRadius * fScale / 2.0, 255 - getGlowColor().GetAlpha())); + const AlphaMask mask(ProcessAndBlurAlphaMask(aAlpha, fDiscreteGlowRadius * fScale / 2.0, + fDiscreteGlowRadius * fScale / 2.0, + 255 - getGlowColor().GetAlpha())); // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask - Bitmap bmp = aBitmapEx.GetBitmap(); + Bitmap bmp(aAlpha.GetSizePixel(), vcl::PixelFormat::N24_BPP); bmp.Erase(getGlowColor()); BitmapEx result(bmp, mask); diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx index 299b6af625ab..67188d8358af 100644 --- a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx @@ -205,26 +205,24 @@ void ShadowPrimitive2D::create2DDecomposition( // limitation to be safe and not go runtime/memory havoc. Use a pretty small // limit due to this is Blurred Shadow functionality and will look good with bitmap // scaling anyways. The value of 250.000 square pixels below maybe adapted as needed. - // NOTE: This may be further optimized. Only the alpha channel is needed, so - // convertToBitmapEx may be split in tooling to have a version that only - // creates the alpha channel. Potential win is >50% for the alpha pixel - // creation step ('>' because alpha painting uses a ColorStack and thus - // often can used simplified rendering) const basegfx::B2DVector aDiscreteClippedSize(rViewInformation.getObjectToViewTransformation() * aClippedRange.getRange()); const sal_uInt32 nDiscreteClippedWidth(ceil(aDiscreteClippedSize.getX())); const sal_uInt32 nDiscreteClippedHeight(ceil(aDiscreteClippedSize.getY())); const geometry::ViewInformation2D aViewInformation2D; const sal_uInt32 nMaximumQuadraticPixels(250000); - const BitmapEx aBitmapEx(::drawinglayer::convertToBitmapEx( + + // I have now added a helper that just creates the mask without having + // to render the content, use it, it's faster + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight, nMaximumQuadraticPixels)); // if we have no shadow, we are done - if (aBitmapEx.IsEmpty()) + if (aAlpha.IsEmpty()) return; - const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel()); + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0)) return; @@ -235,7 +233,7 @@ void ShadowPrimitive2D::create2DDecomposition( if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) { - // scale in X and Y should be the same (see fReduceFactor in convertToBitmapEx), + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), // so adapt numerically to a single scale value, they are integer rounded values const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) / static_cast<double>(nDiscreteClippedWidth)); @@ -245,12 +243,12 @@ void ShadowPrimitive2D::create2DDecomposition( fScale = (fScaleX + fScaleY) * 0.5; } - // Get the Alpha and use as base to blur and apply the effect + // Use the Alpha as base to blur and apply the effect const AlphaMask mask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask( - aBitmapEx.GetAlpha(), 0, fDiscreteBlurRadius * fScale, 0, false)); + aAlpha, 0, fDiscreteBlurRadius * fScale, 0, false)); // The end result is the bitmap filled with blur color and blurred 8-bit alpha mask - Bitmap bmp = aBitmapEx.GetBitmap(); + Bitmap bmp(aAlpha.GetSizePixel(), vcl::PixelFormat::N24_BPP); bmp.Erase(Color(getShadowColor())); BitmapEx result(bmp, mask); diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx index a0807e83f72b..fbb375417be3 100644 --- a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx @@ -28,15 +28,23 @@ #include <basegfx/range/b2drange.hxx> #include <vcl/bitmapex.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> -#include <tools/stream.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> #include <vcl/timer.hxx> #include <vcl/lazydelete.hxx> #include <vcl/dibtools.hxx> #include <vcl/skia/SkiaHelper.hxx> #include <mutex> -// buffered VDev usage +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#endif + +// #define SPEED_COMPARE +#ifdef SPEED_COMPARE +#include <tools/time.hxx> +#endif +// buffered VDev usage namespace { class VDevBuffer : public Timer @@ -45,10 +53,8 @@ private: struct Entry { VclPtr<VirtualDevice> buf; - bool isTransparent = false; - Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent) + Entry(const VclPtr<VirtualDevice>& vdev) : buf(vdev) - , isTransparent(bTransparent) { } }; @@ -72,7 +78,7 @@ public: VDevBuffer(); virtual ~VDevBuffer() override; - VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bTransparent); + VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel); void free(VirtualDevice& rDevice); // Timer virtuals @@ -131,8 +137,7 @@ bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& return false; } -VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel, - bool bTransparent) +VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel) { std::unique_lock aGuard(m_aMutex); VclPtr<VirtualDevice> pRetval; @@ -148,7 +153,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { assert(a->buf && "Empty pointer in VDevBuffer (!)"); - if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent) + if (nBits == a->buf->GetBitCount()) { // candidate is valid due to bit depth if (aFound != maFreeBuffers.end()) @@ -229,9 +234,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize // no success yet, create new buffer if (!pRetval) { - pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT, - bTransparent ? DeviceFormat::DEFAULT - : DeviceFormat::NONE); + pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT); maDeviceTemplates[pRetval] = &rOutDev; pRetval->SetOutputSizePixel(rSizePixel, true); } @@ -243,7 +246,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize } // remember allocated buffer - maUsedBuffers.emplace_back(pRetval, bTransparent); + maUsedBuffers.emplace_back(pRetval); return pRetval; } @@ -278,13 +281,103 @@ void VDevBuffer::Invoke() maFreeBuffers.pop_back(); } } + +#ifdef SPEED_COMPARE +void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel, + OutputDevice& rOutDev) +{ + const int nAvInd(500); + static double fFactors[nAvInd]; + static int nIndex(nAvInd + 1); + static int nRepeat(5); + static int nWorseTotal(0); + static int nBetterTotal(0); + int a(0); + + const Size aSizePixel(rDestPixel.GetSize()); + + // init statics + if (nIndex > nAvInd) + { + for (a = 0; a < nAvInd; a++) + fFactors[a] = 1.0; + nIndex = 0; + } + + // get start time + const sal_uInt64 nTimeA(tools::Time::GetSystemTicks()); + + // loop nRepeat times to get somewhat better timings, else + // numbers are pretty small + for (a = 0; a < nRepeat; a++) + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask)); + } + + // get intermediate time + const sal_uInt64 nTimeB(tools::Time::GetSystemTicks()); + + // loop nRepeat times + for (a = 0; a < nRepeat; a++) + { + // New method using DrawTransformedBitmapEx & fTrans directly + rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()), + BitmapEx(rContent), 1 - fTrans); + } + + // get end time + const sal_uInt64 nTimeC(tools::Time::GetSystemTicks()); + + // calculate deltas + const sal_uInt64 nTimeFormer(nTimeB - nTimeA); + const sal_uInt64 nTimeNew(nTimeC - nTimeB); + + // compare & note down + if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew) + { + if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500) + { + nRepeat += 1; + SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat); + return; + } + + const double fNewFactor((double)nTimeFormer / nTimeNew); + fFactors[nIndex % nAvInd] = fNewFactor; + nIndex++; + double fAverage(0.0); + { + for (a = 0; a < nAvInd; a++) + fAverage += fFactors[a]; + fAverage /= nAvInd; + } + if (fNewFactor < 1.0) + nWorseTotal++; + else + nBetterTotal++; + + char buf[300]; + sprintf(buf, + "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, " + "WorseTotal: %d, BetterTotal: %d)", + nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER", + fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal, + nBetterTotal); + SAL_INFO("drawinglayer.processor2d", buf); + } +} +#endif } // support for rendering Bitmap and BitmapEx contents - namespace drawinglayer { -// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D) +// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D VDevBuffer& getVDevBuffer() { // secure global instance with Vcl's safe destroyer of external (seen by @@ -294,7 +387,7 @@ VDevBuffer& getVDevBuffer() return *aVDevBuffer.get(); } -impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop) +impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange) : mrOutDev(rOutDev) , mpContent(nullptr) , mpAlpha(nullptr) @@ -303,19 +396,26 @@ impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& aRangePixel.transform(mrOutDev.GetViewTransformation()); maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); - if (bCrop) - maDestPixel.Intersection({ {}, mrOutDev.GetOutputSizePixel() }); + maDestPixel.Intersection({ {}, mrOutDev.GetOutputSizePixel() }); if (!isVisible()) return; - mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true); + mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); // #i93485# assert when copying from window to VDev is used SAL_WARN_IF( mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer", "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)"); + // initialize buffer by blitting content of source to prepare for + // transparence/ copying back + const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled()); + mrOutDev.EnableMapMode(false); + mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(), + maDestPixel.GetSize(), mrOutDev); + mrOutDev.EnableMapMode(bWasEnabledSrc); + MapMode aNewMapMode(mrOutDev.GetMapMode()); const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft())); @@ -351,15 +451,13 @@ void impBufferDevice::paint(double fTrans) const Point aEmptyPoint; const Size aSizePixel(maDestPixel.GetSize()); const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled()); -#ifdef DBG_UTIL - static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore -#endif mrOutDev.EnableMapMode(false); mpContent->EnableMapMode(false); #ifdef DBG_UTIL // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) @@ -388,17 +486,75 @@ void impBufferDevice::paint(double fTrans) } #endif - BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel)); - aAlphaMask.BlendWith(aContent.GetAlpha()); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask)); + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); } else if (0.0 != fTrans) { - basegfx::B2DHomMatrix trans, scale; - trans.translate(maDestPixel.TopLeft().X(), maDestPixel.TopLeft().Y()); - scale.scale(aSizePixel.Width(), aSizePixel.Height()); - mrOutDev.DrawTransformedBitmapEx( - trans * scale, mpContent->GetBitmapEx(aEmptyPoint, aSizePixel), 1 - fTrans); + const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + +#ifdef SPEED_COMPARE + static bool bCompareFormerAndNewTimings(true); + + if (bCompareFormerAndNewTimings) + { + doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev); + } + else +#endif + // Note: this extra scope is needed due to 'clang plugin indentation'. It complains + // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'. + // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this. + { + // For the case we have a unified transparency value there is a former + // and new method to paint that which can be used. To decide on measurements, + // I added 'doSpeedCompare' above which can be activated by defining + // SPEED_COMPARE at the top of this file. + // I added the used Testdoc: blurplay3.odg as + // https://bugs.documentfoundation.org/attachment.cgi?id=182463 + // I did measure on + // + // Linux Dbg: + // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934) + // + // Linux Pro: + // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337) + // + // Win Dbg: + // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428) + // + // Win Pro: + // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909) + // + // Note: I am aware that the Dbg are of limited usefulness, but include them here + // for reference. + // + // The important part is "av. last 500 Former/New is %ld" which decribes the averaged factor from Former/New + // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the + // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will + // use Former for now. + // + // To easily allow to change this (maybe system-dependent) I add a static switch here, + // also for evetually experimenting (hint: can be changed in the debugger). + static bool bUseNew(false); + + if (bUseNew) + { + // New method using DrawTransformedBitmapEx & fTrans directly + mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + maDestPixel.TopLeft().X(), + maDestPixel.TopLeft().Y()), + BitmapEx(aContent), 1 - fTrans); + } + else + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); + } + } } else { @@ -422,7 +578,7 @@ VirtualDevice& impBufferDevice::getTransparence() "impBufferDevice: No content, check isVisible() before accessing (!)"); if (!mpAlpha) { - mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false); + mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); mpAlpha->SetMapMode(mpContent->GetMapMode()); // copy AA flag for new target; masking needs to be smooth diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx index 99585b05b141..f893ec79bb93 100644 --- a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx @@ -21,12 +21,73 @@ #include <vcl/virdev.hxx> -namespace basegfx -{ -class B2DRange; -} - -// support methods for vcl direct gradient rendering +// Helper class *exclusively* for VclProcessor2D. It should only +// be used internally, see current four usages. It is used to +// render something with mask or transparence (see MaskPrimitive2D, +// UnifiedTransparencePrimitive2D and TransparencePrimitive2D) or +// as tooling for preparing pixelized output in the renderer +// (see PatternFillPrimitive2D) if that is faster. +// +// To do so, initializing this instance takes over a lot of work +// from you: +// - It initializes a 'Content' VDev which is buffered (since it had +// shown that re-allocating all the time is slower). It checks +// visibility and all usages initializing this should check for +// isVisible() after contruction. +// - It pre-initializes the 'Content' VDev with blitting the content +// of the target VDev. +// - It offers to get a 'Transparence' VDev (also from the buffer) if +// needed. +// +// If 'Transparence' is/was used, it combines as needed to paint +// all buffered stuff to target VDev when calling paint(). +// Caution: It is designed to use *either* a fixed transparence in +// the paint()-call *or* a fill TransparenceChannel using a +// 'Transparence' VDev. It is *not* designed to use/combine +// both - it's simply not needed for it's intended purpose/usage. +// +// Painting transparent works based on a simple principle: It first +// blits the original content of the target VDev. Then the content +// is painted on top of that, plus a Transparence/Mask prepared. +// The combination always works since unchanged pixels will not +// change, independent of the transparence value [0..255] it gets +// mixed with. Or the other way around: Only pixels changed on the +// Content *can* be changed by transparence values. +// +// This is 2.5 times faster than first painting to a +// 'combined' VDev that supports transparency, as is used by the +// presentation engine. +// For the mentioned factor refer to: +// Patch to demonstrate former and now repaint differences +// https://gerrit.libreoffice.org/c/core/+/129301 +// git fetch https://git.libreoffice.org/core refs/changes/01/129301/3 && git cherry-pick FETCH_HEAD +// +// Note: This principle only works when the target is RGB, so +// useful for EditViews like for PrimitiveRenderers where this is +// the case. For usage with GBA tragets the starting conditions +// would have to be modified to not blend against the initial color +// of 'Content' (usually COL_WHITE). +// +// After having finished the rework of ShadowPrimitive2D, +// SoftEdgePrimitive2D and GlowPrimitive2D (see commits:) +// e735ad1c57cddaf17d6ffc0cf15b5e14fa63c4ad +// 707b0c328a282d993fa33b618083d20b6c521de6 +// c2d1458723c66c2fd717a112f89f773226adc841 +// which used the impBufferDevice in such a mode combined with +// mentioned 'combined' transparence VDev it is now possible +// to return to this former, much faster method. +// +// Please do *not* hack/use this helper class, better create +// a new one fitting your/the intended purpose. I do not want +// to see losing performance by this getting modified again. +// +// Note: Using that 'combined' transparence VDev is not really +// recommended, it may vanish anytime. That it works with +// PrimitiveRenderers *at all* is neither designed nor tested +// or recommended - it's pure coincidence. +// +// I hope that for the future all this will vanish by getting to +// fully RGBA-capable devices - what is planned and makes sense. namespace drawinglayer { @@ -38,7 +99,7 @@ class impBufferDevice tools::Rectangle maDestPixel; public: - impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop = true); + impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange); ~impBufferDevice(); void paint(double fTrans = 0.0); diff --git a/drawinglayer/source/tools/converters.cxx b/drawinglayer/source/tools/converters.cxx index 3c4ad791a3c3..48a5b404b056 100644 --- a/drawinglayer/source/tools/converters.cxx +++ b/drawinglayer/source/tools/converters.cxx @@ -35,40 +35,114 @@ #include <vcl/filter/PngImageWriter.hxx> #endif -namespace drawinglayer +namespace { -BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, - const geometry::ViewInformation2D& rViewInformation2D, - sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, - sal_uInt32 nMaxSquarePixels) +bool implPrepareConversion(drawinglayer::primitive2d::Primitive2DContainer& rSequence, + sal_uInt32& rnDiscreteWidth, sal_uInt32& rnDiscreteHeight, + const sal_uInt32 nMaxSquarePixels) { - BitmapEx aRetval; - - if (rSeq.empty()) - return aRetval; + if (rSequence.empty()) + return false; - if (nDiscreteWidth <= 0 || nDiscreteHeight <= 0) - return aRetval; + if (rnDiscreteWidth <= 0 || rnDiscreteHeight <= 0) + return false; - // get destination size in pixels - const MapMode aMapModePixel(MapUnit::MapPixel); - const sal_uInt32 nViewVisibleArea(nDiscreteWidth * nDiscreteHeight); - drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); + const sal_uInt32 nViewVisibleArea(rnDiscreteWidth * rnDiscreteHeight); if (nViewVisibleArea > nMaxSquarePixels) { // reduce render size double fReduceFactor = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea)); - nDiscreteWidth = basegfx::fround(static_cast<double>(nDiscreteWidth) * fReduceFactor); - nDiscreteHeight = basegfx::fround(static_cast<double>(nDiscreteHeight) * fReduceFactor); + rnDiscreteWidth = basegfx::fround(static_cast<double>(rnDiscreteWidth) * fReduceFactor); + rnDiscreteHeight = basegfx::fround(static_cast<double>(rnDiscreteHeight) * fReduceFactor); const drawinglayer::primitive2d::Primitive2DReference aEmbed( new drawinglayer::primitive2d::TransformPrimitive2D( basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor), - std::move(aSequence))); + std::move(rSequence))); - aSequence = drawinglayer::primitive2d::Primitive2DContainer{ aEmbed }; + rSequence = drawinglayer::primitive2d::Primitive2DContainer{ aEmbed }; + } + + return true; +} + +AlphaMask implcreateAlphaMask(drawinglayer::primitive2d::Primitive2DContainer& rSequence, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, + const Size& rSizePixel) +{ + ScopedVclPtrInstance<VirtualDevice> pContent; + + // prepare vdev + if (!pContent->SetOutputSizePixel(rSizePixel, false)) + { + SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << rSizePixel.Width() << "x" + << rSizePixel.Height()); + return AlphaMask(); + } + + // create pixel processor, also already takes care of AAing and + // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If + // not wanted, change after this call as needed + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pContentProcessor + = drawinglayer::processor2d::createPixelProcessor2DFromOutputDevice(*pContent, + rViewInformation2D); + + // prepare for mask creation + pContent->SetMapMode(MapMode(MapUnit::MapPixel)); + + // set alpha to all white (fully transparent) + pContent->Erase(); + + // embed primitives to paint them black + const drawinglayer::primitive2d::Primitive2DReference xRef( + new drawinglayer::primitive2d::ModifiedColorPrimitive2D( + std::move(rSequence), + std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0.0, 0.0, 0.0)))); + const drawinglayer::primitive2d::Primitive2DContainer xSeq{ xRef }; + + // render + pContentProcessor->process(xSeq); + pContentProcessor.reset(); + + // get alpha channel from vdev + pContent->EnableMapMode(false); + const Point aEmptyPoint; + return AlphaMask(pContent->GetBitmap(aEmptyPoint, rSizePixel)); +} +} + +namespace drawinglayer +{ +AlphaMask createAlphaMask(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels) +{ + drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); + + if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels)) + { + return AlphaMask(); + } + + const Size aSizePixel(nDiscreteWidth, nDiscreteHeight); + + return implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel); +} + +BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, + sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight, + sal_uInt32 nMaxSquarePixels) +{ + BitmapEx aRetval; + drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq)); + + if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels)) + { + return aRetval; } const Point aEmptyPoint; @@ -99,7 +173,7 @@ BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSe } // We map to pixel, use that MapMode. Init by erasing. - pContent->SetMapMode(aMapModePixel); + pContent->SetMapMode(MapMode(MapUnit::MapPixel)); pContent->Erase(); // create pixel processor, also already takes care of AAing and @@ -131,6 +205,19 @@ BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSe } #endif + // Combined/mixed usage of VirtualDevice in Alpha-mode here and + // the faster method for divided content/alpha in the VclPixelProcessor2D + // *can* go wrong e.g. for objects filled with TransparenceGradients, + // so unfortunately for now we have to reliably re-create the + // AlphaMask using a method that does this always correct (also used + // now in GlowPrimitive2D and ShadowPrimitive2D which both only need the + // AlphaMask to do their job, so speeding that up, too). + // Note: This could be further approved by creating a small ::B2DProcessor + // that checks if rSeq contains a TransparenceGradient fill and only then use + // this correction. + const AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel)); + aRetval = BitmapEx(aRetval.GetBitmap(), aAlpha); + return aRetval; } diff --git a/include/drawinglayer/converters.hxx b/include/drawinglayer/converters.hxx index 1fda7a2f8350..f85bbfdae6f5 100644 --- a/include/drawinglayer/converters.hxx +++ b/include/drawinglayer/converters.hxx @@ -24,6 +24,18 @@ namespace drawinglayer { +// Helper that just creates the AlphaMask for a given Seq of Primitives. +// If only the mask is needed this can be significantly faster then +// creating content & mask in a BitmapEx (since the creation uses +// e.g. a unified color for gradients instead of having to fully paint +// these) +AlphaMask DRAWINGLAYER_DLLPUBLIC +createAlphaMask(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, + const geometry::ViewInformation2D& rViewInformation2D, sal_uInt32 nDiscreteWidth, + sal_uInt32 nDiscreteHeight, sal_uInt32 nMaxSquarePixels); + +// Helper for convertPrimitive2DContainerToBitmapEx below, but can be also used +// directly BitmapEx DRAWINGLAYER_DLLPUBLIC convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq, const geometry::ViewInformation2D& rViewInformation2D, sal_uInt32 nDiscreteWidth, diff --git a/solenv/clang-format/excludelist b/solenv/clang-format/excludelist index d0790424e6c0..c081d5184433 100644 --- a/solenv/clang-format/excludelist +++ b/solenv/clang-format/excludelist @@ -3254,7 +3254,6 @@ desktop/win32/source/applauncher/launcher.cxx desktop/win32/source/loader.cxx desktop/win32/source/loader.hxx desktop/win32/source/unoinfo.cxx -drawinglayer/inc/converters.hxx drawinglayer/inc/emfplushelper.hxx drawinglayer/inc/primitive2d/cropprimitive2d.hxx drawinglayer/inc/primitive2d/graphicprimitivehelper2d.hxx @@ -3328,7 +3327,6 @@ drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx drawinglayer/source/primitive2d/primitivetools2d.cxx drawinglayer/source/primitive2d/sceneprimitive2d.cxx drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx -drawinglayer/source/primitive2d/shadowprimitive2d.cxx drawinglayer/source/primitive2d/structuretagprimitive2d.cxx drawinglayer/source/primitive2d/svggradientprimitive2d.cxx drawinglayer/source/primitive2d/textbreakuphelper.cxx |