summaryrefslogtreecommitdiff
path: root/drawinglayer
diff options
context:
space:
mode:
authorArmin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de>2022-08-29 13:32:38 +0200
committerArmin Le Grand <Armin.Le.Grand@me.com>2022-08-31 12:12:37 +0200
commitc2d1458723c66c2fd717a112f89f773226adc841 (patch)
tree96d2fe402278348ddec0d434da325105de7bed6f /drawinglayer
parent0885d13cea917e9187adfe6190e60f4ce6039d2a (diff)
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.Grand@me.com>
Diffstat (limited to 'drawinglayer')
-rw-r--r--drawinglayer/Library_drawinglayer.mk1
-rw-r--r--drawinglayer/source/primitive2d/GlowSoftEgdeShadowTools.cxx94
-rw-r--r--drawinglayer/source/primitive2d/glowprimitive2d.cxx298
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx1
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.cxx150
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.hxx1
6 files changed, 399 insertions, 146 deletions
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);