Rework of ShadowPrimitive2D

This is pretty much the same for ShadowPrimitive2D as the
change for GlowPrimitive2D and SoftEdgePrimitive2D, so for
more comments please refer to those commits:

 c2d1458723c66c2fd717a112f89f773226adc841
 707b0c328a282d993fa33b618083d20b6c521de6

There are some needed differences due to ShadowPrimitive2D
having existed longer and is used for non-blurred shadow
for a long time and is used as unchanged as possible.
Only for active glow of shadow is a buffering and local
decompose used.

Change-Id: I55e6516f59390079356ac16f24743b474e53fb05
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139858
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
diff --git a/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx
index da3621a..0a32493 100644
--- a/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx
+++ b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx
@@ -17,7 +17,7 @@
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
#include "GlowSoftEgdeShadowTools.hxx"
#include <vcl/bitmapex.hxx>
#include <vcl/BitmapFilter.hxx>
#include <vcl/BitmapBasicMorphologyFilter.hxx>
diff --git a/include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx b/drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.hxx
similarity index 100%
rename from include/drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx
rename to drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.hxx
diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
index f8c5037..44a97c5 100644
--- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx
@@ -24,7 +24,7 @@
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <drawinglayer/converters.hxx>
#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
#include "GlowSoftEgdeShadowTools.hxx"

#ifdef DBG_UTIL
#include <tools/stream.hxx>
diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
index 15deebf..0702c6c 100644
--- a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx
@@ -22,73 +22,383 @@
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <drawinglayer/converters.hxx>
#include "GlowSoftEgdeShadowTools.hxx"

#ifdef DBG_UTIL
#include <tools/stream.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#endif

#include <memory>
#include <utility>

using namespace com::sun::star;


namespace drawinglayer::primitive2d
{
        ShadowPrimitive2D::ShadowPrimitive2D(
            basegfx::B2DHomMatrix aShadowTransform,
            const basegfx::BColor& rShadowColor,
            double fShadowBlur,
            Primitive2DContainer&& aChildren)
        :   GroupPrimitive2D(std::move(aChildren)),
            maShadowTransform(std::move(aShadowTransform)),
            maShadowColor(rShadowColor),
            mfShadowBlur(fShadowBlur)
ShadowPrimitive2D::ShadowPrimitive2D(basegfx::B2DHomMatrix aShadowTransform,
                                     const basegfx::BColor& rShadowColor, double fShadowBlur,
                                     Primitive2DContainer&& aChildren)
    : BufferedDecompositionGroupPrimitive2D(std::move(aChildren))
    , maShadowTransform(std::move(aShadowTransform))
    , maShadowColor(rShadowColor)
    , mfShadowBlur(fShadowBlur)
    , mfLastDiscreteBlurRadius(0.0)
    , maLastClippedRange()
{
}

bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
{
    if (BufferedDecompositionGroupPrimitive2D::operator==(rPrimitive))
    {
        const ShadowPrimitive2D& rCompare = static_cast<const ShadowPrimitive2D&>(rPrimitive);

        return (getShadowTransform() == rCompare.getShadowTransform()
                && getShadowColor() == rCompare.getShadowColor()
                && getShadowBlur() == rCompare.getShadowBlur());
    }

    return false;
}

// Helper to get the to-be-shadowed geometry completely embedded to
// a ModifiedColorPrimitive2D (change to ShadowColor) and TransformPrimitive2D
// (direction/offset/transformation of shadow). Since this is used pretty
// often, pack into a helper
void ShadowPrimitive2D::getFullyEmbeddedShadowPrimitives(Primitive2DContainer& rContainer) const
{
    if (getChildren().empty())
        return;

    // create a modifiedColorPrimitive containing the shadow color and the content
    const basegfx::BColorModifierSharedPtr aBColorModifier
        = std::make_shared<basegfx::BColorModifier_replace>(getShadowColor());
    const Primitive2DReference xRefA(
        new ModifiedColorPrimitive2D(Primitive2DContainer(getChildren()), aBColorModifier));
    Primitive2DContainer aSequenceB{ xRefA };

    // build transformed primitiveVector with shadow offset and add to target
    rContainer.visit(new TransformPrimitive2D(getShadowTransform(), std::move(aSequenceB)));
}

bool ShadowPrimitive2D::prepareValuesAndcheckValidity(
    basegfx::B2DRange& rBlurRange, basegfx::B2DRange& rClippedRange,
    basegfx::B2DVector& rDiscreteBlurSize, double& rfDiscreteBlurRadius,
    const geometry::ViewInformation2D& rViewInformation) const
{
    // no BlurRadius defined, done
    if (getShadowBlur() <= 0.0)
        return false;

    // no geometry, done
    if (getChildren().empty())
        return false;

    // no pixel target, done
    if (rViewInformation.getObjectToViewTransformation().isIdentity())
        return false;

    // get fully embedded ShadowPrimitive
    Primitive2DContainer aEmbedded;
    getFullyEmbeddedShadowPrimitives(aEmbedded);

    // get geometry range that defines area that needs to be pixelated
    rBlurRange = aEmbedded.getB2DRange(rViewInformation);

    // no range of geometry, done
    if (rBlurRange.isEmpty())
        return false;

    // extend range by BlurRadius in all directions
    rBlurRange.grow(getShadowBlur());

    // initialize ClippedRange to full BlurRange -> all is visible
    rClippedRange = rBlurRange;

    // 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 BlurRadius to ensure needed parts are included
        basegfx::B2DRange aVisibleArea(rViewInformation.getViewport());
        aVisibleArea.grow(getShadowBlur());

        // calculate ClippedRange
        rClippedRange.intersect(aVisibleArea);

        // if BlurRange is completely outside of VisibleArea, ClippedRange
        // will be empty and we are done
        if (rClippedRange.isEmpty())
            return false;
    }

    // calculate discrete pixel size of BlurRange. If it's too small to visualize, we are done
    rDiscreteBlurSize = rViewInformation.getObjectToViewTransformation() * rBlurRange.getRange();
    if (ceil(rDiscreteBlurSize.getX()) < 2.0 || ceil(rDiscreteBlurSize.getY()) < 2.0)
        return false;

    // calculate discrete pixel size of BlurRadius. If it's too small to visualize, we are done
    rfDiscreteBlurRadius = ceil(
        (rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(getShadowBlur(), 0))
            .getLength());
    if (rfDiscreteBlurRadius < 1.0)
        return false;

    return true;
}

void ShadowPrimitive2D::create2DDecomposition(
    Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const
{
    if (getShadowBlur() <= 0.0)
    {
        // Normal (non-blurred) shadow is already completely
        // handled by get2DDecomposition and not buffered. It
        // does not need to be since it's a simple embedding
        // to a ModifiedColorPrimitive2D and TransformPrimitive2D
        return;
    }

    // from here on we process a blurrred shadow
    basegfx::B2DRange aBlurRange;
    basegfx::B2DRange aClippedRange;
    basegfx::B2DVector aDiscreteBlurSize;
    double fDiscreteBlurRadius(0.0);

    // Check various validity details and calculate/prepare values. If false, we are done
    if (!prepareValuesAndcheckValidity(aBlurRange, aClippedRange, aDiscreteBlurSize,
                                       fDiscreteBlurRadius, 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 BlurRange
    const sal_uInt32 nDiscreteBlurWidth(ceil(aDiscreteBlurSize.getX()));
    const sal_uInt32 nDiscreteBlurHeight(ceil(aDiscreteBlurSize.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 BlurRange
    aEmbedding.scale(nDiscreteBlurWidth / aBlurRange.getWidth(),
                     nDiscreteBlurHeight / aBlurRange.getHeight());

    // Get fully embedded ShadowPrimitives. This will also embed to
    // ModifiedColorPrimitive2D (what is not urgently needed) to create
    // the alpha channel, but a paint with all colors set to a single
    // one (like shadowColor here) is often less expensive due to possible
    // simplifications painting the primitves (e.g. gradient)
    Primitive2DContainer aEmbedded;
    getFullyEmbeddedShadowPrimitives(aEmbedded);

    // Embed content graphics to TransformPrimitive2D
    const primitive2d::Primitive2DReference xEmbedRef(
        new primitive2d::TransformPrimitive2D(aEmbedding, std::move(aEmbedded)));
    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 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(
        std::move(xEmbedSeq), aViewInformation2D, nDiscreteClippedWidth, nDiscreteClippedHeight,
        nMaximumQuadraticPixels));

    // if we have no shadow, we are done
    if (aBitmapEx.IsEmpty())
        return;

    const Size& rBitmapExSizePixel(aBitmapEx.GetSizePixel());
    if (!(rBitmapExSizePixel.Width() > 0 && rBitmapExSizePixel.Height() > 0))
        return;

    // We may have to take a corrective scaling into account when the
    // MaximumQuadraticPixel limit was used/triggered
    double fScale(1.0);

    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),
        // 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));
        const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height())
                             / static_cast<double>(nDiscreteClippedHeight));

        fScale = (fScaleX + fScaleY) * 0.5;
    }

    // Get the Alpha and use as base to blur and apply the effect
    const AlphaMask mask(drawinglayer::primitive2d::ProcessAndBlurAlphaMask(
        aBitmapEx.GetAlpha(), 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();
    bmp.Erase(Color(getShadowColor()));
    BitmapEx result(bmp, mask);

#ifdef DBG_UTIL
    static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
    if (bDoSaveForVisualControl)
    {
        // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
        static const OUString sDumpPath(
            OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
        if (!sDumpPath.isEmpty())
        {
            SvFileStream aNew(sDumpPath + "test_shadowblur.png",
                              StreamMode::WRITE | StreamMode::TRUNC);
            vcl::PngImageWriter aPNGWriter(aNew);
            aPNGWriter.write(result);
        }
    }
#endif

        bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
    // Independent from discrete sizes of blur alpha creation, always
    // map and project blur result to geometry range extended by blur
    // 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 };
}

void ShadowPrimitive2D::get2DDecomposition(
    Primitive2DDecompositionVisitor& rVisitor,
    const geometry::ViewInformation2D& rViewInformation) const
{
    if (getShadowBlur() <= 0.0)
    {
        // normal (non-blurred) shadow
        if (getChildren().empty())
            return;

        // get fully embedded ShadowPrimitives
        Primitive2DContainer aEmbedded;
        getFullyEmbeddedShadowPrimitives(aEmbedded);

        rVisitor.visit(aEmbedded);
        return;
    }

    // here we have a blurrred shadow, check conditions of last
    // buffered decompose and decide re-use or re-create by using
    // setBuffered2DDecomposition to reset local buffered version
    basegfx::B2DRange aBlurRange;
    basegfx::B2DRange aClippedRange;
    basegfx::B2DVector aDiscreteBlurSize;
    double fDiscreteBlurRadius(0.0);

    // Check various validity details and calculate/prepare values. If false, we are done
    if (!prepareValuesAndcheckValidity(aBlurRange, aClippedRange, aDiscreteBlurSize,
                                       fDiscreteBlurRadius, rViewInformation))
        return;

    if (!getBuffered2DDecomposition().empty())
    {
        // First check is to detect if the last created decompose is capable
        // to represent the now requested visualization (see similar
        // implementation at GlowPrimitive2D).
        if (!maLastClippedRange.isEmpty() && !maLastClippedRange.isInside(aClippedRange))
        {
            if(BasePrimitive2D::operator==(rPrimitive))
            {
                const ShadowPrimitive2D& rCompare = static_cast< const ShadowPrimitive2D& >(rPrimitive);
            basegfx::B2DRange aLastClippedRangeAndHairline(maLastClippedRange);

                return (getShadowTransform() == rCompare.getShadowTransform()
                    && getShadowColor() == rCompare.getShadowColor()
                    && getShadowBlur() == rCompare.getShadowBlur());
            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);
            }

            return false;
            if (!aLastClippedRangeAndHairline.isInside(aClippedRange))
            {
                // Conditions of last local decomposition have changed, delete
                const_cast<ShadowPrimitive2D*>(this)->setBuffered2DDecomposition(
                    Primitive2DContainer());
            }
        }
    }

        basegfx::B2DRange ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
    if (!getBuffered2DDecomposition().empty())
    {
        // Second check is to react on changes of the DiscreteSoftRadius when
        // zooming in/out (see similar implementation at ShadowPrimitive2D).
        bool bFree(mfLastDiscreteBlurRadius <= 0.0 || fDiscreteBlurRadius <= 0.0);

        if (!bFree)
        {
            basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation));
            aRetval.grow(getShadowBlur());
            aRetval.transform(getShadowTransform());
            return aRetval;
            const double fDiff(fabs(mfLastDiscreteBlurRadius - fDiscreteBlurRadius));
            const double fLen(fabs(mfLastDiscreteBlurRadius) + fabs(fDiscreteBlurRadius));
            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;
        }

        void ShadowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const
        if (bFree)
        {
            if(getChildren().empty())
                return;

            // create a modifiedColorPrimitive containing the shadow color and the content
            const basegfx::BColorModifierSharedPtr aBColorModifier =
                std::make_shared<basegfx::BColorModifier_replace>(
                    getShadowColor());
            const Primitive2DReference xRefA(
                new ModifiedColorPrimitive2D(
                    Primitive2DContainer(getChildren()),
                    aBColorModifier));
            Primitive2DContainer aSequenceB { xRefA };

            // build transformed primitiveVector with shadow offset and add to target
            rVisitor.visit(new TransformPrimitive2D(getShadowTransform(), std::move(aSequenceB)));
            // Conditions of last local decomposition have changed, delete
            const_cast<ShadowPrimitive2D*>(this)->setBuffered2DDecomposition(
                Primitive2DContainer());
        }
    }

        // provide unique ID
        sal_uInt32 ShadowPrimitive2D::getPrimitive2DID() const
        {
            return PRIMITIVE2D_ID_SHADOWPRIMITIVE2D;
        }
    if (getBuffered2DDecomposition().empty())
    {
        // refresh last used DiscreteBlurRadius and ClippedRange to new remembered values
        const_cast<ShadowPrimitive2D*>(this)->mfLastDiscreteBlurRadius = fDiscreteBlurRadius;
        const_cast<ShadowPrimitive2D*>(this)->maLastClippedRange = aClippedRange;
    }

    // call parent, that will check for empty, call create2DDecomposition and
    // set as decomposition
    BufferedDecompositionGroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
}

basegfx::B2DRange
ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
{
    // 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));

    if (getShadowBlur() > 0.0)
    {
        // blurred shadow, that extends the geometry
        aRetval.grow(getShadowBlur());
    }

    aRetval.transform(getShadowTransform());
    return aRetval;
}

// provide unique ID
sal_uInt32 ShadowPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_SHADOWPRIMITIVE2D; }

} // end of namespace

diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
index 55cddb9..f01b675 100644
--- a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx
@@ -24,7 +24,7 @@
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <drawinglayer/converters.hxx>
#include <drawinglayer/primitive2d/GlowSoftEgdeShadowTools.hxx>
#include "GlowSoftEgdeShadowTools.hxx"

#ifdef DBG_UTIL
#include <tools/stream.hxx>
@@ -196,7 +196,7 @@ void SoftEdgePrimitive2D::create2DDecomposition(
        BitmapEx result(aBitmapEx.GetBitmap(), aMask);

#ifdef DBG_UTIL
        static bool bDoSaveForVisualControl(true); // loplugin:constvars:ignore
        static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
        if (bDoSaveForVisualControl)
        {
            // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
index 9268357..8e221aff 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -928,11 +928,6 @@ void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimi
                static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate));
            break;
        }
        case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
        {
            processPrimitive2DOnPixelProcessor(rCandidate);
            break;
        }
        default:
        {
            // process recursively
@@ -2368,103 +2363,6 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D(
    }
}

VclPtr<VirtualDevice>
VclMetafileProcessor2D::CreateBufferDevice(const basegfx::B2DRange& rCandidateRange,
                                           geometry::ViewInformation2D& rViewInfo,
                                           tools::Rectangle& rRectLogic, Size& rSizePixel) const
{
    constexpr double fMaxSquarePixels = 500000;
    basegfx::B2DRange aViewRange(rCandidateRange);
    aViewRange.transform(maCurrentTransformation);
    rRectLogic = tools::Rectangle(static_cast<tools::Long>(std::floor(aViewRange.getMinX())),
                                  static_cast<tools::Long>(std::floor(aViewRange.getMinY())),
                                  static_cast<tools::Long>(std::ceil(aViewRange.getMaxX())),
                                  static_cast<tools::Long>(std::ceil(aViewRange.getMaxY())));
    const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(rRectLogic));
    rSizePixel = aRectPixel.GetSize();
    const double fViewVisibleArea(rSizePixel.getWidth() * rSizePixel.getHeight());
    double fReduceFactor(1.0);

    if (fViewVisibleArea > fMaxSquarePixels)
    {
        // reduce render size
        fReduceFactor = sqrt(fMaxSquarePixels / fViewVisibleArea);
        rSizePixel = Size(basegfx::fround(rSizePixel.getWidth() * fReduceFactor),
                          basegfx::fround(rSizePixel.getHeight() * fReduceFactor));
    }

    VclPtrInstance<VirtualDevice> pBufferDevice(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
    if (pBufferDevice->SetOutputSizePixel(rSizePixel))
    {
        // create and set MapModes for target devices
        MapMode aNewMapMode(mpOutputDevice->GetMapMode());
        aNewMapMode.SetOrigin(Point(-rRectLogic.Left(), -rRectLogic.Top()));
        pBufferDevice->SetMapMode(aNewMapMode);

        // prepare view transformation for target renderers
        // ATTENTION! Need to apply another scaling because of the potential DPI differences
        // between Printer and VDev (mpOutputDevice and pBufferDevice here).
        // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used.
        basegfx::B2DHomMatrix aViewTransform(pBufferDevice->GetViewTransformation());
        const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
        const Size aDPINew(pBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
        const double fDPIXChange(static_cast<double>(aDPIOld.getWidth())
                                 / static_cast<double>(aDPINew.getWidth()));
        const double fDPIYChange(static_cast<double>(aDPIOld.getHeight())
                                 / static_cast<double>(aDPINew.getHeight()));

        if (!basegfx::fTools::equal(fDPIXChange, 1.0) || !basegfx::fTools::equal(fDPIYChange, 1.0))
        {
            aViewTransform.scale(fDPIXChange, fDPIYChange);
        }

        // also take scaling from Size reduction into account
        if (!basegfx::fTools::equal(fReduceFactor, 1.0))
        {
            aViewTransform.scale(fReduceFactor, fReduceFactor);
        }

        // create view information and pixel renderer. Reuse known ViewInformation
        // except new transformation and range
        rViewInfo = geometry::ViewInformation2D(
            getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange,
            getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime());
    }
    else
        pBufferDevice.disposeAndClear();

#if HAVE_P1155R3
    return pBufferDevice;
#else
    return std::move(pBufferDevice);
#endif
}

void VclMetafileProcessor2D::processPrimitive2DOnPixelProcessor(
    const primitive2d::BasePrimitive2D& rCandidate)
{
    basegfx::B2DRange aViewRange(rCandidate.getB2DRange(getViewInformation2D()));
    geometry::ViewInformation2D aViewInfo;
    tools::Rectangle aRectLogic;
    Size aSizePixel;
    auto pBufferDevice(CreateBufferDevice(aViewRange, aViewInfo, aRectLogic, aSizePixel));
    if (pBufferDevice)
    {
        VclPixelProcessor2D aBufferProcessor(aViewInfo, *pBufferDevice, maBColorModifierStack);

        // draw content using pixel renderer
        primitive2d::Primitive2DReference aRef(
            &const_cast<primitive2d::BasePrimitive2D&>(rCandidate));
        aBufferProcessor.process({ aRef });
        const BitmapEx aBmContent(pBufferDevice->GetBitmapEx(Point(), aSizePixel));
        mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), aBmContent);

        // aBufferProcessor dtor pops state off pBufferDevice pushed on by its ctor, let
        // pBufferDevice live past aBufferProcessor scope to avoid warnings
    }
    pBufferDevice.disposeAndClear();
}

} // end of namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
index 06fd61e..0393039 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
@@ -144,10 +144,6 @@ private:
        const primitive2d::TransparencePrimitive2D& rTransparenceCandidate);
    void processStructureTagPrimitive2D(
        const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate);
    void processPrimitive2DOnPixelProcessor(const primitive2d::BasePrimitive2D& rCandidate);
    VclPtr<VirtualDevice> CreateBufferDevice(const basegfx::B2DRange& rCandidateRange,
                                             geometry::ViewInformation2D& rViewInfo,
                                             tools::Rectangle& rRectLogic, Size& rSizePixel) const;

    /// Convert the fWidth to the same space as its coordinates.
    double getTransformedLineWidth(double fWidth) const;
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 12e0449..cf8d7dc 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -58,7 +58,6 @@
#include <drawinglayer/primitive2d/epsprimitive2d.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>
@@ -395,12 +394,6 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv
                static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
            break;
        }
        case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
        {
            processShadowPrimitive2D(
                static_cast<const drawinglayer::primitive2d::ShadowPrimitive2D&>(rCandidate));
            break;
        }
        case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
        {
            processFillGradientPrimitive2D(
@@ -949,52 +942,6 @@ void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrim
    }
}

void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate)
{
    if (rCandidate.getShadowBlur() == 0)
    {
        process(rCandidate);
        return;
    }

    basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
    aRange.transform(maCurrentTransformation);
    basegfx::B2DVector aBlurRadiusVector(rCandidate.getShadowBlur(), 0);
    aBlurRadiusVector *= maCurrentTransformation;
    const double fBlurRadius = aBlurRadiusVector.getLength();

    impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
    if (aBufferDevice.isVisible() && !aRange.isEmpty())
    {
        OutputDevice* pLastOutputDevice = mpOutputDevice;
        mpOutputDevice = &aBufferDevice.getContent();

        process(rCandidate);

        const tools::Rectangle aRect(static_cast<tools::Long>(std::floor(aRange.getMinX())),
                                     static_cast<tools::Long>(std::floor(aRange.getMinY())),
                                     static_cast<tools::Long>(std::ceil(aRange.getMaxX())),
                                     static_cast<tools::Long>(std::ceil(aRange.getMaxY())));

        BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());

        AlphaMask mask = drawinglayer::primitive2d::ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0,
                                                                            fBlurRadius, 0, false);

        const basegfx::BColor aShadowColor(
            maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor()));

        Bitmap bitmap = bitmapEx.GetBitmap();
        bitmap.Erase(Color(aShadowColor));
        BitmapEx result(bitmap, mask);

        mpOutputDevice = pLastOutputDevice;
        mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
    }
    else
        SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
}

void VclPixelProcessor2D::processFillGradientPrimitive2D(
    const primitive2d::FillGradientPrimitive2D& rPrimitive)
{
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
index 9f1e11c..c144ba9 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
@@ -39,9 +39,6 @@ class PolygonStrokePrimitive2D;
class FillHatchPrimitive2D;
class BackgroundColorPrimitive2D;
class BorderLinePrimitive2D;
class GlowPrimitive2D;
class ShadowPrimitive2D;
class SoftEdgePrimitive2D;
class FillGradientPrimitive2D;
class PatternFillPrimitive2D;
}
@@ -97,7 +94,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 processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate);
    void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive);
    void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive);

diff --git a/include/drawinglayer/primitive2d/glowprimitive2d.hxx b/include/drawinglayer/primitive2d/glowprimitive2d.hxx
index 6a60c85..985137e 100644
--- a/include/drawinglayer/primitive2d/glowprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/glowprimitive2d.hxx
@@ -21,7 +21,6 @@

#include <drawinglayer/drawinglayerdllapi.h>

#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx>
#include <tools/color.hxx>

diff --git a/include/drawinglayer/primitive2d/shadowprimitive2d.hxx b/include/drawinglayer/primitive2d/shadowprimitive2d.hxx
index 45a97a9..79f2f30 100644
--- a/include/drawinglayer/primitive2d/shadowprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/shadowprimitive2d.hxx
@@ -21,14 +21,12 @@

#include <drawinglayer/drawinglayerdllapi.h>

#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx>
#include <basegfx/color/bcolor.hxx>


namespace drawinglayer::primitive2d
{
        /** ShadowPrimitive2D class
/** ShadowPrimitive2D class

            This primitive defines a generic shadow geometry construction
            for 2D objects. It decomposes to a TransformPrimitive2D embedded
@@ -42,44 +40,60 @@ namespace drawinglayer::primitive2d
            are needed for the shadow itself; all the local decompositions of the
            original geometry can be reused from the renderer for shadow visualisation.
        */
        class DRAWINGLAYER_DLLPUBLIC ShadowPrimitive2D final : public GroupPrimitive2D
        {
        private:
            /// the shadow transformation, normally just an offset
            basegfx::B2DHomMatrix                   maShadowTransform;
class DRAWINGLAYER_DLLPUBLIC ShadowPrimitive2D final : public BufferedDecompositionGroupPrimitive2D
{
private:
    /// the shadow transformation, normally just an offset
    basegfx::B2DHomMatrix maShadowTransform;

            /// the shadow color to which all geometry is to be forced
            basegfx::BColor                         maShadowColor;
    /// the shadow color to which all geometry is to be forced
    basegfx::BColor maShadowColor;

            /// the blur radius of the shadow
            double mfShadowBlur;
    /// the blur radius of the shadow
    double mfShadowBlur;

    /// last used DiscreteBlurRadius and ClippedRange
    double mfLastDiscreteBlurRadius;
    basegfx::B2DRange maLastClippedRange;

    public:
            /// constructor
            ShadowPrimitive2D(
                basegfx::B2DHomMatrix aShadowTransform,
                const basegfx::BColor& rShadowColor,
                double fShadowBlur,
                Primitive2DContainer&& aChildren);
    /// helpers
    void getFullyEmbeddedShadowPrimitives(Primitive2DContainer& rContainer) const;
    bool prepareValuesAndcheckValidity(basegfx::B2DRange& rRange, basegfx::B2DRange& rClippedRange,
                                       basegfx::B2DVector& rDiscreteSize,
                                       double& rfDiscreteBlurRadius,
                                       const geometry::ViewInformation2D& rViewInformation) const;

            /// data read access
            const basegfx::B2DHomMatrix& getShadowTransform() const { return maShadowTransform; }
            const basegfx::BColor& getShadowColor() const { return maShadowColor; }
            double getShadowBlur() const { return mfShadowBlur; }
            /// compare operator
            virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
protected:
    /** 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 override;

            /// get range
            virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
public:
    /// constructor
    ShadowPrimitive2D(basegfx::B2DHomMatrix aShadowTransform, const basegfx::BColor& rShadowColor,
                      double fShadowBlur, Primitive2DContainer&& aChildren);

            ///  create decomposition
            virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override;
    /// data read access
    const basegfx::B2DHomMatrix& getShadowTransform() const { return maShadowTransform; }
    const basegfx::BColor& getShadowColor() const { return maShadowColor; }
    double getShadowBlur() const { return mfShadowBlur; }

            /// provide unique ID
            virtual sal_uInt32 getPrimitive2DID() const override;
        };
    /// compare operator
    virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;

    /// get range
    virtual basegfx::B2DRange
    getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;

    ///  create decomposition
    virtual void
    get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor,
                       const geometry::ViewInformation2D& rViewInformation) const override;

    /// provide unique ID
    virtual sal_uInt32 getPrimitive2DID() const override;
};
} // end of namespace drawinglayer::primitive2d


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */