drawinglayer/Library_drawinglayer.mk                         |    1 
 drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx  |   94 +++
 drawinglayer/source/primitive2d/glowprimitive2d.cxx          |  298 ++++++++++-
 drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx   |    1 
 drawinglayer/source/processor2d/vclpixelprocessor2d.cxx      |  150 -----
 drawinglayer/source/processor2d/vclpixelprocessor2d.hxx      |    1 
 include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx |   42 +
 include/drawinglayer/primitive2d/glowprimitive2d.hxx         |   35 +
 8 files changed, 476 insertions(+), 146 deletions(-)

New commits:
commit c2d1458723c66c2fd717a112f89f773226adc841
Author:     Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de>
AuthorDate: Mon Aug 29 13:32:38 2022 +0200
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Wed Aug 31 12:12:37 2022 +0200

    Rework of GlowPrimitive2D
    
    The new version does all needed stuff inside the GlowPrimitive2D
    implementation. Advantages are:
    
    - there is no need anymore to handle directly in the renderer
      implementations. That includes HitTest & future renderers, but
      also the currently existing fallback from MetafileRenderer
      to PixelRenderer
    
    - the buffered B2Primitive can re-use the last, potentially
      expensively cerated pixelation result
    
    - it checks for the possibility to do so using various
      aspects (see implementation, more would be possible)
    
    - it no longer uses impBufferDevice in the not wanted
      double-vdev/alpha-channel mode from presentation engine
    
    - it offers an example how to do all this with just a
      primitive (that can be replaced with another impl if
      needed without having to adapt any renderers). To
      support that, I added plenty of comments
    
    The group of GlowPrimitive2D, SoftEdgePrimitive2D and
    ShadowPrimitive2D use impBufferDevice in that much slower
    and expensive mode (two VDevs, processor-based alpha
    mixing). To get back to faster transparence rendering
    in general this is a 1st step, we also will need to
    re-work the other two mentioned primitives.
    The reworked one is now more efficient.
    
    Change-Id: I25c6fb970682b5311ce6f9ca4abf2702fb7c8862
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138973
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>

diff --git a/drawinglayer/Library_drawinglayer.mk 
b/drawinglayer/Library_drawinglayer.mk
index 2430852d33bd..2b618f73136d 100644
--- a/drawinglayer/Library_drawinglayer.mk
+++ b/drawinglayer/Library_drawinglayer.mk
@@ -88,6 +88,7 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\
     drawinglayer/source/primitive2d/fillgradientprimitive2d \
     drawinglayer/source/primitive2d/fillhatchprimitive2d \
     drawinglayer/source/primitive2d/glowprimitive2d \
+    drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools \
     drawinglayer/source/primitive2d/graphicprimitivehelper2d \
     drawinglayer/source/primitive2d/graphicprimitive2d \
     drawinglayer/source/primitive2d/gridprimitive2d \
diff --git a/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx 
b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx
new file mode 100644
index 000000000000..0cc5be1bd532
--- /dev/null
+++ b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+
+namespace drawinglayer::primitive2d
+{
+/* Returns 8-bit alpha mask created from passed mask.
+
+   Negative fErodeDilateRadius values mean erode, positive - dilate.
+   nTransparency defines minimal transparency level.
+*/
+AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double 
fErodeDilateRadius,
+                                  double fBlurRadius, sal_uInt8 nTransparency, 
bool bConvertTo1Bit)
+{
+    // Only completely white pixels on the initial mask must be considered for 
transparency. Any
+    // other color must be treated as black. This creates 1-bit B&W bitmap.
+    BitmapEx mask(bConvertTo1Bit ? rMask.CreateMask(COL_WHITE) : rMask);
+
+    // Scaling down increases performance without noticeable quality loss. 
Additionally,
+    // current blur implementation can only handle blur radius between 2 and 
254.
+    Size aSize = mask.GetSizePixel();
+    double fScale = 1.0;
+    while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
+    {
+        fScale /= 2;
+        fBlurRadius /= 2;
+        fErodeDilateRadius /= 2;
+        aSize.setHeight(aSize.Height() / 2);
+        aSize.setWidth(aSize.Width() / 2);
+    }
+
+    // BmpScaleFlag::Fast is important for following color replacement
+    mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
+
+    if (fErodeDilateRadius > 0)
+        BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
+    else if (fErodeDilateRadius < 0)
+        BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 
0xFF));
+
+    if (nTransparency)
+    {
+        const Color aTransparency(nTransparency, nTransparency, nTransparency);
+        mask.Replace(COL_BLACK, aTransparency);
+    }
+
+    // We need 8-bit grey mask for blurring
+    mask.Convert(BmpConversion::N8BitGreys);
+
+    // calculate blurry effect
+    BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
+
+    mask.Scale(rMask.GetSizePixel());
+
+    return AlphaMask(mask.GetBitmap());
+}
+
+drawinglayer::geometry::ViewInformation2D
+expandB2DRangeAtViewInformation2D(const 
drawinglayer::geometry::ViewInformation2D& rViewInfo,
+                                  double nAmount)
+{
+    basegfx::B2DRange viewport(rViewInfo.getViewport());
+    viewport.grow(nAmount);
+    return { rViewInfo.getObjectTransformation(),
+             rViewInfo.getViewTransformation(),
+             viewport,
+             rViewInfo.getVisualizedPage(),
+             rViewInfo.getViewTime(),
+             rViewInfo.getReducedDisplayQuality() };
+}
+
+} // end of namespace drawinglayer::primitive2d
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx 
b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
index 6fe14c2222c1..f7194dba7d9f 100644
--- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
@@ -18,7 +18,18 @@
  */
 
 #include <drawinglayer/primitive2d/glowprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <drawinglayer/converters.hxx>
+#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
+
+#ifdef DBG_UTIL
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#endif
 
 using namespace com::sun::star;
 
@@ -27,8 +38,11 @@ namespace drawinglayer::primitive2d
 GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius,
                                  Primitive2DContainer&& rChildren)
     : GroupPrimitive2D(std::move(rChildren))
+    , maBuffered2DDecomposition()
     , maGlowColor(rGlowColor)
     , mfGlowRadius(fRadius)
+    , mfLastDiscreteGlowRadius(0.0)
+    , maLastClippedRange()
 {
 }
 
@@ -45,12 +59,294 @@ bool GlowPrimitive2D::operator==(const BasePrimitive2D& 
rPrimitive) const
     return false;
 }
 
+bool GlowPrimitive2D::prepareValuesAndcheckValidity(
+    basegfx::B2DRange& rGlowRange, basegfx::B2DRange& rClippedRange,
+    basegfx::B2DVector& rDiscreteGlowSize, double& rfDiscreteGlowRadius,
+    const geometry::ViewInformation2D& rViewInformation) const
+{
+    // no GlowRadius defined, done
+    if (getGlowRadius() <= 0.0)
+        return false;
+
+    // no geometry, done
+    if (getChildren().empty())
+        return false;
+
+    // no pixel target, done
+    if (rViewInformation.getObjectToViewTransformation().isIdentity())
+        return false;
+
+    // get geometry range that defines area that needs to be pixelated
+    rGlowRange = getChildren().getB2DRange(rViewInformation);
+
+    // no range of geometry, done
+    if (rGlowRange.isEmpty())
+        return false;
+
+    // extend range by GlowRadius in all directions
+    rGlowRange.grow(getGlowRadius());
+
+    // initialize ClippedRange to full GlowRange -> all is visible
+    rClippedRange = rGlowRange;
+
+    // get Viewport and check if used. If empty, all is visible (see
+    // ViewInformation2D definition in viewinformation2d.hxx)
+    if (!rViewInformation.getViewport().isEmpty())
+    {
+        // if used, extend by GlowRadus to ensure needed parts are included
+        basegfx::B2DRange aVisibleArea(rViewInformation.getViewport());
+        aVisibleArea.grow(getGlowRadius());
+
+        // calculate ClippedRange
+        rClippedRange.intersect(aVisibleArea);
+
+        // if GlowRange is completely outside of VisibleArea, ClippedRange
+        // will be empty and we are done
+        if (rClippedRange.isEmpty())
+            return false;
+    }
+
+    // calculate discrete pixel size of GlowRange. If it's too small to 
visualize, we are done
+    rDiscreteGlowSize = rViewInformation.getObjectToViewTransformation() * 
rGlowRange.getRange();
+    if (ceil(rDiscreteGlowSize.getX()) < 2.0 || ceil(rDiscreteGlowSize.getY()) 
< 2.0)
+        return false;
+
+    // calculate discrete pixel size of GlowRadius. If it's too small to 
visualize, we are done
+    rfDiscreteGlowRadius = ceil(
+        (rViewInformation.getObjectToViewTransformation() * 
basegfx::B2DVector(getGlowRadius(), 0))
+            .getLength());
+    if (rfDiscreteGlowRadius < 1.0)
+        return false;
+
+    return true;
+}
+
+void GlowPrimitive2D::create2DDecomposition(
+    Primitive2DContainer& rContainer, const geometry::ViewInformation2D& 
rViewInformation) const
+{
+    basegfx::B2DRange aGlowRange;
+    basegfx::B2DRange aClippedRange;
+    basegfx::B2DVector aDiscreteGlowSize;
+    double fDiscreteGlowRadius(0.0);
+
+    // Check various validity details and calculate/prepare values. If false, 
we are done
+    if (!prepareValuesAndcheckValidity(aGlowRange, aClippedRange, 
aDiscreteGlowSize,
+                                       fDiscreteGlowRadius, rViewInformation))
+        return;
+
+    // Create embedding transformation from object to top-left zero-aligned
+    // target pixel geometry (discrete form of ClippedRange)
+    // First, move to top-left of GlowRange
+    const sal_uInt32 nDiscreteGlowWidth(ceil(aDiscreteGlowSize.getX()));
+    const sal_uInt32 nDiscreteGlowHeight(ceil(aDiscreteGlowSize.getY()));
+    basegfx::B2DHomMatrix 
aEmbedding(basegfx::utils::createTranslateB2DHomMatrix(
+        -aClippedRange.getMinX(), -aClippedRange.getMinY()));
+    // Second, scale to discrete bitmap size
+    // Even when using the offset from ClippedRange, we need to use the
+    // scaling from the full representation, thus from GlowRange
+    aEmbedding.scale(nDiscreteGlowWidth / aGlowRange.getWidth(),
+                     nDiscreteGlowHeight / aGlowRange.getHeight());
+
+    // Embed content graphics to TransformPrimitive2D
+    const primitive2d::Primitive2DReference xEmbedRef(
+        new primitive2d::TransformPrimitive2D(aEmbedding, 
Primitive2DContainer(getChildren())));
+    primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef };
+
+    // Create BitmapEx using drawinglayer tooling, including a 
MaximumQuadraticPixel
+    // 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(
+        std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, 
nDiscreteClippedHeight,
+        nMaximumQuadraticPixels));
+
+    if (!aBitmapEx.IsEmpty())
+    {
+        const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel());
+
+        if (rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0)
+        {
+            // We may have to take a corrective scaling into account when the
+            // MaximumQuadraticPixel limit was used/triggered
+            double fScaleX(1.0);
+            double fScaleY(1.0);
+
+            if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != 
nDiscreteClippedWidth)
+            {
+                fScaleX = static_cast<double>(rBitmapExSizePixel.Width())
+                          / static_cast<double>(nDiscreteClippedWidth);
+            }
+
+            if (static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != 
nDiscreteClippedHeight)
+            {
+                fScaleY = static_cast<double>(rBitmapExSizePixel.Height())
+                          / static_cast<double>(nDiscreteClippedHeight);
+            }
+
+            // fDiscreteGlowRadius is the size of the halo from each side of 
the object. The halo is the
+            // border of glow color that fades from glow transparency level to 
fully transparent
+            // 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 * fScaleX / 2.0,
+                fDiscreteGlowRadius * fScaleY / 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();
+            bmp.Erase(getGlowColor());
+            BitmapEx result(bmp, mask);
+
+#ifdef DBG_UTIL
+            static bool bDoSaveForVisualControl(false); // 
loplugin:constvars:ignore
+            if (bDoSaveForVisualControl)
+            {
+                SvFileStream aNew(
+#ifdef _WIN32
+                    "c:\\test_glow.png"
+#else
+                    "~/test_glow.png"
+#endif
+                    ,
+                    StreamMode::WRITE | StreamMode::TRUNC);
+                vcl::PngImageWriter aPNGWriter(aNew);
+                aPNGWriter.write(result);
+            }
+#endif
+
+            // Independent from discrete sizes of glow alpha creation, always
+            // map and project glow result to geometry range extended by glow
+            // radius, but to the eventually clipped instance (ClippedRange)
+            const primitive2d::Primitive2DReference xEmbedRefBitmap(
+                new BitmapPrimitive2D(VCLUnoHelper::CreateVCLXBitmap(result),
+                                      
basegfx::utils::createScaleTranslateB2DHomMatrix(
+                                          aClippedRange.getWidth(), 
aClippedRange.getHeight(),
+                                          aClippedRange.getMinX(), 
aClippedRange.getMinY())));
+
+            rContainer = primitive2d::Primitive2DContainer{ xEmbedRefBitmap };
+        }
+    }
+}
+
+// Same as in BufferedDecompositionPrimitive2D, maybe we need a tooling class
+// like BufferedDecompositionGropupPrimitive2D if this is used more often
+// (AFAIR it's similar for ScenePrimitive2D which also does quite some re-use/
+// buffering checks to avoid too much re-creation)
+void GlowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& 
rVisitor,
+                                         const geometry::ViewInformation2D& 
rViewInformation) const
+{
+    basegfx::B2DRange aGlowRange;
+    basegfx::B2DRange aClippedRange;
+    basegfx::B2DVector aDiscreteGlowSize;
+    double fDiscreteGlowRadius(0.0);
+
+    // Check various validity details and calculate/prepare values. If false, 
we are done
+    if (!prepareValuesAndcheckValidity(aGlowRange, aClippedRange, 
aDiscreteGlowSize,
+                                       fDiscreteGlowRadius, rViewInformation))
+        return;
+
+    if (!getBuffered2DDecomposition().empty())
+    {
+        // First check is to detect if the last created decompose is capable
+        // to represent the now requested visualization.
+        // ClippedRange is the needed visualizationArea for the current glow
+        // effect, LastClippedRange is the one from the existing/last 
rendering.
+        // Check if last created area is sufficient and can be re-used
+        if (!maLastClippedRange.isEmpty() && 
!maLastClippedRange.isInside(aClippedRange))
+        {
+            // To avoid unnecessssary invalidations due to being *very* correct
+            // with HairLines (which are view-dependent and thus change the
+            // result(s) here slightly when changing zoom), add a slight 
unsharp
+            // component if we have a ViewTransform. The derivation is inside
+            // the range of half a pixel (due to one pixel hairline)
+            basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange);
+
+            if (!rViewInformation.getObjectToViewTransformation().isIdentity())
+            {
+                // Grow by view-dependent size of 1/2 pixel
+                const double 
fHalfPixel((rViewInformation.getInverseObjectToViewTransformation()
+                                         * basegfx::B2DVector(0.5, 0))
+                                            .getLength());
+                aLastClippedRangeAndHairline.grow(fHalfPixel);
+            }
+
+            if (!aLastClippedRangeAndHairline.isInside(aClippedRange))
+            {
+                // Conditions of last local decomposition have changed, delete
+                const_cast<GlowPrimitive2D*>(this)->setBuffered2DDecomposition(
+                    Primitive2DContainer());
+            }
+        }
+    }
+
+    if (!getBuffered2DDecomposition().empty())
+    {
+        // Second check is to react on changes of the DiscreteGlowRadius when
+        // zooming in/out.
+        // Use the known last and current DiscreteGlowRadius to decide
+        // if the visualization can be re-used. Be a little 'creative' here
+        // and make it dependent on a *relative* change - it is not necessary
+        // to re-create everytime if the exact value is missed since zooming
+        // pixel-based glow effect is pretty good due to it's smooth nature
+        bool bFree(mfLastDiscreteGlowRadius <= 0.0 || fDiscreteGlowRadius <= 
0.0);
+
+        if (!bFree)
+        {
+            const double fDiff(fabs(mfLastDiscreteGlowRadius - 
fDiscreteGlowRadius));
+            const double fLen(fabs(mfLastDiscreteGlowRadius) + 
fabs(fDiscreteGlowRadius));
+            const double fRelativeChange(fDiff / fLen);
+
+            // Use lower fixed values here to change more often, higher to 
change less often.
+            // Value is in the range of ]0.0 .. 1.0]
+            bFree = fRelativeChange >= 0.15;
+        }
+
+        if (bFree)
+        {
+            // Conditions of last local decomposition have changed, delete
+            
const_cast<GlowPrimitive2D*>(this)->setBuffered2DDecomposition(Primitive2DContainer());
+        }
+    }
+
+    if (getBuffered2DDecomposition().empty())
+    {
+        // refresh last used DiscreteGlowRadius and ClippedRange to new 
remembered values
+        const_cast<GlowPrimitive2D*>(this)->mfLastDiscreteGlowRadius = 
fDiscreteGlowRadius;
+        const_cast<GlowPrimitive2D*>(this)->maLastClippedRange = aClippedRange;
+
+        // create decomposition
+        Primitive2DContainer aNewSequence;
+
+        create2DDecomposition(aNewSequence, rViewInformation);
+        
const_cast<GlowPrimitive2D*>(this)->setBuffered2DDecomposition(std::move(aNewSequence));
+    }
+
+    rVisitor.visit(getBuffered2DDecomposition());
+}
+
 basegfx::B2DRange
 GlowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& 
rViewInformation) const
 {
-    basegfx::B2DRange aRetval(GroupPrimitive2D::getB2DRange(rViewInformation));
+    // Hint: Do *not* use GroupPrimitive2D::getB2DRange, that will 
(unnecessarily)
+    // use the decompose - what works, but is not needed here.
+    // We know the to-be-visualized geometry and the radius it needs to be 
extended,
+    // so simply calculate the exact needed range.
+    basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation));
+
     // We need additional space for the glow from all sides
     aRetval.grow(getGlowRadius());
+
     return aRetval;
 }
 
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
index cfa1ec8c1863..96e6daa66ab5 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -929,7 +929,6 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const 
primitive2d::BasePrimi
             break;
         }
         case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
-        case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
         case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
         {
             processPrimitive2DOnPixelProcessor(rCandidate);
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 5e0612f94a93..d3184a0e0d7a 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -23,8 +23,6 @@
 #include <comphelper/lok.hxx>
 
 #include <sal/log.hxx>
-#include <vcl/BitmapBasicMorphologyFilter.hxx>
-#include <vcl/BitmapFilterStackBlur.hxx>
 #include <vcl/outdev.hxx>
 #include <vcl/hatch.hxx>
 #include <vcl/canvastools.hxx>
@@ -61,6 +59,7 @@
 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
 #include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
+#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
 
 #include <com/sun/star/awt/XWindow2.hpp>
 #include <com/sun/star/awt/XControl.hpp>
@@ -407,12 +406,6 @@ void VclPixelProcessor2D::processBasePrimitive2D(const 
primitive2d::BasePrimitiv
                 static_cast<const 
drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
             break;
         }
-        case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
-        {
-            processGlowPrimitive2D(
-                static_cast<const 
drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate));
-            break;
-        }
         case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
         {
             processSoftEdgePrimitive2D(
@@ -973,144 +966,13 @@ void 
VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrim
     }
 }
 
-namespace
-{
-/* Returns 8-bit alpha mask created from passed mask.
-
-   Negative fErodeDilateRadius values mean erode, positive - dilate.
-   nTransparency defines minimal transparency level.
-*/
-AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double 
fErodeDilateRadius,
-                                  double fBlurRadius, sal_uInt8 nTransparency,
-                                  bool bConvertTo1Bit = true)
-{
-    // Only completely white pixels on the initial mask must be considered for 
transparency. Any
-    // other color must be treated as black. This creates 1-bit B&W bitmap.
-    BitmapEx mask(bConvertTo1Bit ? rMask.CreateMask(COL_WHITE) : rMask);
-
-    // Scaling down increases performance without noticeable quality loss. 
Additionally,
-    // current blur implementation can only handle blur radius between 2 and 
254.
-    Size aSize = mask.GetSizePixel();
-    double fScale = 1.0;
-    while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
-    {
-        fScale /= 2;
-        fBlurRadius /= 2;
-        fErodeDilateRadius /= 2;
-        aSize.setHeight(aSize.Height() / 2);
-        aSize.setWidth(aSize.Width() / 2);
-    }
-
-    // BmpScaleFlag::Fast is important for following color replacement
-    mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
-
-    if (fErodeDilateRadius > 0)
-        BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
-    else if (fErodeDilateRadius < 0)
-        BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 
0xFF));
-
-    if (nTransparency)
-    {
-        const Color aTransparency(nTransparency, nTransparency, nTransparency);
-        mask.Replace(COL_BLACK, aTransparency);
-    }
-
-    // We need 8-bit grey mask for blurring
-    mask.Convert(BmpConversion::N8BitGreys);
-
-    // calculate blurry effect
-    BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
-
-    mask.Scale(rMask.GetSizePixel());
-
-    return AlphaMask(mask.GetBitmap());
-}
-
-drawinglayer::geometry::ViewInformation2D
-expandRange(const drawinglayer::geometry::ViewInformation2D& rViewInfo, double 
nAmount)
-{
-    basegfx::B2DRange viewport(rViewInfo.getViewport());
-    viewport.grow(nAmount);
-    return { rViewInfo.getObjectTransformation(),
-             rViewInfo.getViewTransformation(),
-             viewport,
-             rViewInfo.getVisualizedPage(),
-             rViewInfo.getViewTime(),
-             rViewInfo.getReducedDisplayQuality() };
-}
-}
-
-void VclPixelProcessor2D::processGlowPrimitive2D(const 
primitive2d::GlowPrimitive2D& rCandidate)
-{
-    const double nGlowRadius(rCandidate.getGlowRadius());
-    // Avoid wrong effect on the cut-off side; so expand by radius
-    const auto aExpandedViewInfo(expandRange(getViewInformation2D(), 
nGlowRadius));
-    basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo));
-    aRange.transform(maCurrentTransformation);
-    basegfx::B2DVector aGlowRadiusVector(nGlowRadius, 0);
-    // Calculate the pixel size of glow radius in current transformation
-    aGlowRadiusVector *= maCurrentTransformation;
-    // Glow radius is the size of the halo from each side of the object. The 
halo is the
-    // border of glow color that fades from glow transparency level to fully 
transparent
-    // 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.
-    const double fBlurRadius = aGlowRadiusVector.getLength() / 2;
-    // Consider glow transparency (initial transparency near the object edge)
-    const sal_uInt8 nAlpha = rCandidate.getGlowColor().GetAlpha();
-
-    impBufferDevice aBufferDevice(*mpOutputDevice, aRange, false);
-    if (aBufferDevice.isVisible())
-    {
-        // remember last OutDev and set to content
-        OutputDevice* pLastOutputDevice = mpOutputDevice;
-        mpOutputDevice = &aBufferDevice.getContent();
-        // We don't need antialiased mask here, which would only make effect 
thicker
-        const auto aPrevAA = mpOutputDevice->GetAntialiasing();
-        mpOutputDevice->SetAntialiasing(AntialiasingFlags::NONE);
-        process(rCandidate);
-
-        // Limit the bitmap size to the visible area.
-        basegfx::B2DRange bitmapRange(aRange);
-        bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport());
-        if (!bitmapRange.isEmpty())
-        {
-            const tools::Rectangle aRect(
-                static_cast<tools::Long>(std::floor(bitmapRange.getMinX())),
-                static_cast<tools::Long>(std::floor(bitmapRange.getMinY())),
-                static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())),
-                static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY())));
-            BitmapEx bmpEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), 
aRect.GetSize());
-            mpOutputDevice->SetAntialiasing(aPrevAA);
-
-            AlphaMask mask
-                = ProcessAndBlurAlphaMask(bmpEx.GetAlpha(), fBlurRadius, 
fBlurRadius, 255 - nAlpha);
-
-            // The end result is the bitmap filled with glow color and blurred 
8-bit alpha mask
-            const basegfx::BColor aGlowColor(
-                
maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor()));
-            Bitmap bmp = bmpEx.GetBitmap();
-            bmp.Erase(Color(aGlowColor));
-            BitmapEx result(bmp, mask);
-
-            // back to old OutDev
-            mpOutputDevice = pLastOutputDevice;
-            mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
-        }
-        else
-        {
-            mpOutputDevice = pLastOutputDevice;
-        }
-    }
-    else
-        SAL_WARN("drawinglayer", "Temporary buffered virtual device is not 
visible");
-}
-
 void VclPixelProcessor2D::processSoftEdgePrimitive2D(
     const primitive2d::SoftEdgePrimitive2D& rCandidate)
 {
     const double nRadius(rCandidate.getRadius());
     // Avoid wrong effect on the cut-off side; so expand by diameter
-    const auto aExpandedViewInfo(expandRange(getViewInformation2D(), nRadius * 
2));
+    const auto 
aExpandedViewInfo(::drawinglayer::primitive2d::expandB2DRangeAtViewInformation2D(
+        getViewInformation2D(), nRadius * 2));
 
     basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo));
     aRange.transform(maCurrentTransformation);
@@ -1143,7 +1005,8 @@ void VclPixelProcessor2D::processSoftEdgePrimitive2D(
             BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), 
aRect.GetSize());
 
             AlphaMask aMask = bitmap.GetAlpha();
-            AlphaMask blurMask = ProcessAndBlurAlphaMask(aMask, -fBlurRadius, 
fBlurRadius, 0);
+            AlphaMask blurMask = 
drawinglayer::primitive2d::ProcessAndBlurAlphaMask(
+                aMask, -fBlurRadius, fBlurRadius, 0);
 
             aMask.BlendWith(blurMask);
 
@@ -1192,7 +1055,8 @@ void VclPixelProcessor2D::processShadowPrimitive2D(const 
primitive2d::ShadowPrim
 
         BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), 
aRect.GetSize());
 
-        AlphaMask mask = ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0, 
fBlurRadius, 0, false);
+        AlphaMask mask = 
drawinglayer::primitive2d::ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0,
+                                                                            
fBlurRadius, 0, false);
 
         const basegfx::BColor aShadowColor(
             
maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor()));
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx 
b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
index eaf212c8e5b1..e083e951afbf 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
@@ -98,7 +98,6 @@ class VclPixelProcessor2D final : public VclProcessor2D
     processBorderLinePrimitive2D(const 
drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder);
     void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& 
rCandidate);
     void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& 
rCandidate);
-    void processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& 
rCandidate);
     void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& 
rCandidate);
     void processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& 
rCandidate);
     void processFillGradientPrimitive2D(const 
primitive2d::FillGradientPrimitive2D& rPrimitive);
diff --git a/include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx 
b/include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx
new file mode 100644
index 000000000000..61079728d841
--- /dev/null
+++ b/include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/alpha.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+
+namespace drawinglayer::primitive2d
+{
+/* Returns 8-bit alpha mask created from passed mask.
+
+   Negative fErodeDilateRadius values mean erode, positive - dilate.
+   nTransparency defines minimal transparency level.
+*/
+AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double 
fErodeDilateRadius,
+                                  double fBlurRadius, sal_uInt8 nTransparency,
+                                  bool bConvertTo1Bit = true);
+
+drawinglayer::geometry::ViewInformation2D
+expandB2DRangeAtViewInformation2D(const 
drawinglayer::geometry::ViewInformation2D& rViewInfo,
+                                  double nAmount);
+
+} // end of namespace drawinglayer::primitive2d
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/drawinglayer/primitive2d/glowprimitive2d.hxx 
b/include/drawinglayer/primitive2d/glowprimitive2d.hxx
index 7dd65e7252dc..1fbc5f73652c 100644
--- a/include/drawinglayer/primitive2d/glowprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/glowprimitive2d.hxx
@@ -29,12 +29,42 @@ namespace drawinglayer::primitive2d
 class DRAWINGLAYER_DLLPUBLIC GlowPrimitive2D final : public GroupPrimitive2D
 {
 private:
+    /// a sequence used for buffering the last create2DDecomposition() result
+    Primitive2DContainer maBuffered2DDecomposition;
+
     /// the Glow color to which all geometry is to be forced; includes alpha
     Color maGlowColor;
 
     /// the Glow size, in logical units (100ths of mm)
     double mfGlowRadius;
 
+    /// last used DiscreteGlowRadius and ClippedRange
+    double mfLastDiscreteGlowRadius;
+    basegfx::B2DRange maLastClippedRange;
+
+    /// helpers
+    bool prepareValuesAndcheckValidity(basegfx::B2DRange& rRange, 
basegfx::B2DRange& rClippedRange,
+                                       basegfx::B2DVector& rDiscreteSize,
+                                       double& rfDiscreteGlowRadius,
+                                       const geometry::ViewInformation2D& 
rViewInformation) const;
+
+protected:
+    /** access methods to maBuffered2DDecomposition, same as in
+     *  BufferedDecompositionPrimitive2D
+     */
+    const Primitive2DContainer& getBuffered2DDecomposition() const
+    {
+        return maBuffered2DDecomposition;
+    }
+    void setBuffered2DDecomposition(Primitive2DContainer&& rNew)
+    {
+        maBuffered2DDecomposition = std::move(rNew);
+    }
+
+    /** method which is to be used to implement the local decomposition of a 
2D primitive. */
+    virtual void create2DDecomposition(Primitive2DContainer& rContainer,
+                                       const geometry::ViewInformation2D& 
rViewInformation) const;
+
 public:
     /// constructor
     GlowPrimitive2D(const Color& rGlowColor, double fRadius, 
Primitive2DContainer&& rChildren);
@@ -50,6 +80,11 @@ public:
     virtual basegfx::B2DRange
     getB2DRange(const geometry::ViewInformation2D& rViewInformation) const 
override;
 
+    /// The default implementation will return an empty sequence
+    virtual void
+    get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor,
+                       const geometry::ViewInformation2D& rViewInformation) 
const override;
+
     /// provide unique ID
     virtual sal_uInt32 getPrimitive2DID() const override;
 };
  • [Libreoffice-commits] core.git:... Armin Le Grand (allotropia) (via logerrit)

Reply via email to