diff options
author | Armin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de> | 2022-08-29 13:32:38 +0200 |
---|---|---|
committer | Armin Le Grand <Armin.Le.Grand@me.com> | 2022-08-31 12:12:37 +0200 |
commit | c2d1458723c66c2fd717a112f89f773226adc841 (patch) | |
tree | 96d2fe402278348ddec0d434da325105de7bed6f /drawinglayer | |
parent | 0885d13cea917e9187adfe6190e60f4ce6039d2a (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')
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); |