diff --git a/drawinglayer/source/attribute/sdrglowattribute.cxx b/drawinglayer/source/attribute/sdrglowattribute.cxx index 4295aef23ae3..90367ff67ea0 100644 --- a/drawinglayer/source/attribute/sdrglowattribute.cxx +++ b/drawinglayer/source/attribute/sdrglowattribute.cxx @@ -21,7 +21,7 @@ namespace drawinglayer { namespace attribute { -SdrGlowAttribute::SdrGlowAttribute(sal_Int32 nRadius, const basegfx::BColor& rColor) +SdrGlowAttribute::SdrGlowAttribute(sal_Int32 nRadius, const Color& rColor) : m_nRadius(nRadius) , m_color(rColor) { @@ -44,31 +44,6 @@ bool SdrGlowAttribute::operator==(const SdrGlowAttribute& rCandidate) const return m_nRadius == rCandidate.m_nRadius && m_color == rCandidate.m_color; } -const basegfx::B2DHomMatrix& SdrGlowAttribute::GetTransfMatrix(basegfx::B2DRange nRange) const -{ - if (!m_oTransfCache) - { - double dRadius100mm = static_cast(m_nRadius) / 360.0; - // Apply a scaling with the center point of the shape as origin. - // 1) translate shape to the origin - basegfx::B2DHomMatrix matrix = basegfx::utils::createCoordinateSystemTransform( - nRange.getCenter(), basegfx::B2DVector(-1, 0), basegfx::B2DVector(0, -1)); - - basegfx::B2DHomMatrix inverse(matrix); - inverse.invert(); - - // 2) Scale up - double scale_x = (nRange.getWidth() + dRadius100mm) / nRange.getWidth(); - double scale_y = (nRange.getHeight() + dRadius100mm) / nRange.getHeight(); - matrix *= basegfx::utils::createScaleB2DHomMatrix(scale_x, scale_y); - - // 3) Translate shape back to its place - matrix *= inverse; - m_oTransfCache = std::move(matrix); - } - return *m_oTransfCache; -} - } // end of namespace attribute } // end of namespace drawinglayer diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx index 7d6c23a322ad..bf49b8e215b5 100644 --- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -30,12 +30,11 @@ using namespace com::sun::star; namespace drawinglayer::primitive2d { -GlowPrimitive2D::GlowPrimitive2D(const basegfx::B2DHomMatrix& rGlowTransform, - const basegfx::BColor& rGlowColor, +GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius, const Primitive2DContainer& rChildren) : GroupPrimitive2D(rChildren) - , maGlowTransform(rGlowTransform) , maGlowColor(rGlowColor) + , mfGlowRadius(fRadius) { } @@ -45,7 +44,7 @@ bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { const GlowPrimitive2D& rCompare = static_cast(rPrimitive); - return (getGlowTransform() == rCompare.getGlowTransform() + return (getGlowRadius() == rCompare.getGlowRadius() && getGlowColor() == rCompare.getGlowColor()); } @@ -55,8 +54,9 @@ bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const basegfx::B2DRange GlowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const { - basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); - aRetval.transform(getGlowTransform()); + basegfx::B2DRange aRetval(GroupPrimitive2D::getB2DRange(rViewInformation)); + // We need additional space for the glow from all sides + aRetval.grow(getGlowRadius()); return aRetval; } @@ -67,15 +67,13 @@ void GlowPrimitive2D::get2DDecomposition( if (getChildren().empty()) return; - // create a modifiedColorPrimitive containing the Glow color and the content + // create a modifiedColorPrimitive containing the *black* color and the content. Using black + // on white allows creating useful mask in VclPixelProcessor2D::processGlowPrimitive2D. basegfx::BColorModifierSharedPtr aBColorModifier - = std::make_shared(getGlowColor()); + = std::make_shared(basegfx::BColor()); - const Primitive2DReference xRefA(new ModifiedColorPrimitive2D(getChildren(), aBColorModifier)); - const Primitive2DContainer aSequenceB{ xRefA }; - - // build transformed primitiveVector with Glow offset and add to target - rVisitor.append(new TransformPrimitive2D(getGlowTransform(), aSequenceB)); + const Primitive2DReference xRef(new ModifiedColorPrimitive2D(getChildren(), aBColorModifier)); + rVisitor.append(xRef); } // provide unique ID diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index bba66e2d4272..6c14c09c1a19 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -23,7 +23,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -912,42 +915,73 @@ void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitiv { basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); aRange.transform(maCurrentTransformation); - aRange.grow(10.0); + basegfx::B2DVector aGlowRadiusVector(rCandidate.getGlowRadius(), 0); + // Calculate the pixel size of glow radius in current transformation + aGlowRadiusVector *= maCurrentTransformation; + const double fGlowRadius = aGlowRadiusVector.getLength(); impBufferDevice aBufferDevice(*mpOutputDevice, aRange); if (aBufferDevice.isVisible()) { // remember last OutDev and set to content OutputDevice* pLastOutputDevice = mpOutputDevice; - mpOutputDevice = &aBufferDevice.getTransparence(); - // paint content to virtual device + mpOutputDevice = &aBufferDevice.getContent(); + // Processing will draw whatever geometry on white background, applying *black* + // replacement color. The black color replacement is added in 2d decomposition of + // glow primitive. mpOutputDevice->Erase(); process(rCandidate); - - // obtain result as a bitmap - auto bitmap = mpOutputDevice->GetBitmapEx(Point(aRange.getMinX(), aRange.getMinY()), + Bitmap bitmap = mpOutputDevice->GetBitmap(Point(aRange.getMinX(), aRange.getMinY()), Size(aRange.getWidth(), aRange.getHeight())); - constexpr double nRadius = 5.0; - bitmap.Scale(Size(aRange.getWidth() - nRadius, aRange.getHeight() - nRadius)); - // use bitmap later as mask - auto mask = bitmap.GetBitmap(); - mpOutputDevice = &aBufferDevice.getContent(); - process(rCandidate); - bitmap = mpOutputDevice->GetBitmapEx(Point(aRange.getMinX(), aRange.getMinY()), - Size(aRange.getWidth(), aRange.getHeight())); - bitmap.Scale(Size(aRange.getWidth() - nRadius, aRange.getHeight() - nRadius)); + BitmapEx mask(bitmap); // copy the bitmap to mask + // Only completely transparent parts will be completely white; only those must be + // considered white on the initial B&W alpha mask. Any other color must be treated + // as black. + BitmapFilter::Filter(mask, BitmapMonochromeFilter(255)); + + // 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; + // 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. + double fBlurRadius = fGlowRadius / 2; + while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000) + { + fScale /= 2; + fBlurRadius /= 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); + + // Dilate the black pixels using blur radius, to make blur start at actual object margins. + // This differentiates glow from blurry shadow; so potentially extend this function to also + // handle blurry shadow, and conditionally skip this step + BitmapFilter::Filter(mask, BitmapDilateFilter(fBlurRadius)); + + // We need 8-bit grey mask for blurring + mask.Convert(BmpConversion::N8BitGreys); + // Consider glow transparency (initial transparency near the object edge) + const sal_uInt8 nTransparency = rCandidate.getGlowColor().GetTransparency(); + const Color aTransparency(nTransparency, nTransparency, nTransparency); + BitmapFilter::Filter(mask, BitmapColorReplaceFilter(COL_BLACK, aTransparency)); // calculate blurry effect - BitmapFilterStackBlur glowFilter(nRadius); - BitmapFilter::Filter(bitmap, glowFilter); + BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius)); + + // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask + bitmap.Erase(rCandidate.getGlowColor()); + // alpha mask will be scaled up automatically to match bitmap + BitmapEx result(bitmap, AlphaMask(mask.GetBitmap())); + // back to old OutDev mpOutputDevice = pLastOutputDevice; - mpOutputDevice->DrawBitmapEx( - Point(aRange.getMinX() - nRadius / 2, aRange.getMinY() - nRadius / 2), - BitmapEx(bitmap.GetBitmap(), mask)); - - // paint result - //aBufferDevice.paint(); + mpOutputDevice->DrawBitmapEx(Point(aRange.getMinX(), aRange.getMinY()), result); } else SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible"); diff --git a/include/drawinglayer/attribute/sdrglowattribute.hxx b/include/drawinglayer/attribute/sdrglowattribute.hxx index f5120c1a2044..b17560bd76af 100644 --- a/include/drawinglayer/attribute/sdrglowattribute.hxx +++ b/include/drawinglayer/attribute/sdrglowattribute.hxx @@ -15,8 +15,7 @@ #include #include #include - -#include +#include namespace drawinglayer { @@ -26,11 +25,10 @@ class DRAWINGLAYER_DLLPUBLIC SdrGlowAttribute { private: sal_Int32 m_nRadius = 0; - mutable std::optional m_oTransfCache; - basegfx::BColor m_color; + Color m_color; // Includes alpha! public: - SdrGlowAttribute(sal_Int32 nRadius, const basegfx::BColor& rColor); + SdrGlowAttribute(sal_Int32 nRadius, const Color& rColor); SdrGlowAttribute(); SdrGlowAttribute(const SdrGlowAttribute&); SdrGlowAttribute(SdrGlowAttribute&&); @@ -41,10 +39,9 @@ public: SdrGlowAttribute& operator=(SdrGlowAttribute&&); // data access - const basegfx::B2DHomMatrix& GetTransfMatrix(basegfx::B2DRange nCenter) const; - const basegfx::BColor& getColor() const { return m_color; }; - sal_Int32 getRadius() const { return m_nRadius; }; - bool isDefault() const { return m_nRadius == 0; }; + const Color& getColor() const { return m_color; } + sal_Int32 getRadius() const { return m_nRadius; } + bool isDefault() const { return m_nRadius == 0; } }; } // end of namespace attribute } // end of namespace drawinglayer diff --git a/include/drawinglayer/primitive2d/glowprimitive2d.hxx b/include/drawinglayer/primitive2d/glowprimitive2d.hxx index 0c77a9a94a6c..1aacdf0dde68 100644 --- a/include/drawinglayer/primitive2d/glowprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/glowprimitive2d.hxx @@ -25,6 +25,7 @@ #include #include #include +#include namespace drawinglayer { @@ -33,20 +34,19 @@ namespace primitive2d class DRAWINGLAYER_DLLPUBLIC GlowPrimitive2D final : public GroupPrimitive2D { private: - /// the Glow transformation, normally just an offset - basegfx::B2DHomMatrix maGlowTransform; + /// the Glow color to which all geometry is to be forced; includes alpha + Color maGlowColor; - /// the Glow color to which all geometry is to be forced - basegfx::BColor maGlowColor; + /// the Glow size, in logical units (100ths of mm) + double mfGlowRadius; public: /// constructor - GlowPrimitive2D(const basegfx::B2DHomMatrix& rGlowTransform, const basegfx::BColor& rGlowColor, - const Primitive2DContainer& rChildren); + GlowPrimitive2D(const Color& rGlowColor, double fRadius, const Primitive2DContainer& rChildren); /// data read access - const basegfx::B2DHomMatrix& getGlowTransform() const { return maGlowTransform; } - const basegfx::BColor& getGlowColor() const { return maGlowColor; } + const Color& getGlowColor() const { return maGlowColor; } + double getGlowRadius() const { return mfGlowRadius; } /// compare operator virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; diff --git a/include/vcl/BitmapFilterStackBlur.hxx b/include/vcl/BitmapFilterStackBlur.hxx index 425420ded3c2..8ac6a47e968e 100644 --- a/include/vcl/BitmapFilterStackBlur.hxx +++ b/include/vcl/BitmapFilterStackBlur.hxx @@ -18,10 +18,9 @@ class VCL_DLLPUBLIC BitmapFilterStackBlur : public BitmapFilter { sal_Int32 mnRadius; - bool mbExtend; public: - BitmapFilterStackBlur(sal_Int32 nRadius, bool bExtend = true); + BitmapFilterStackBlur(sal_Int32 nRadius); virtual ~BitmapFilterStackBlur(); virtual BitmapEx execute(BitmapEx const& rBitmap) const override; diff --git a/svx/source/sdr/primitive2d/sdrattributecreator.cxx b/svx/source/sdr/primitive2d/sdrattributecreator.cxx index ddfddd5998b4..bb43198bf4f6 100644 --- a/svx/source/sdr/primitive2d/sdrattributecreator.cxx +++ b/svx/source/sdr/primitive2d/sdrattributecreator.cxx @@ -341,9 +341,12 @@ namespace drawinglayer::primitive2d if(!bGlow) return attribute::SdrGlowAttribute(); sal_Int32 nRadius = rSet.Get(SDRATTR_GLOW_RAD).GetValue(); - const Color aColor(rSet.Get(SDRATTR_GLOW_COLOR).GetColorValue()); + Color aColor(rSet.Get(SDRATTR_GLOW_COLOR).GetColorValue()); + sal_uInt16 nTransparency(rSet.Get(SDRATTR_GLOW_TRANSPARENCY).GetValue()); + if (nTransparency) + aColor.SetTransparency(std::round(nTransparency / 100.0 * 255.0)); - attribute::SdrGlowAttribute glowAttr{ nRadius, aColor.getBColor() }; + attribute::SdrGlowAttribute glowAttr{ nRadius, aColor }; return glowAttr; } diff --git a/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx index 5ce394f8c813..193d94492756 100644 --- a/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx +++ b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx @@ -540,10 +540,7 @@ namespace drawinglayer::primitive2d const uno::Sequence< beans::PropertyValue > xViewParameters; geometry::ViewInformation2D aViewInformation2D(xViewParameters); aRetval[0] = Primitive2DReference( - new GlowPrimitive2D( - rGlow.GetTransfMatrix(rContent.getB2DRange(aViewInformation2D)), - rGlow.getColor(), - rContent)); + new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius() / 360.0, rContent)); aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent)); return aRetval; } diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx index fec21fa118f0..dddfaf571dd4 100644 --- a/vcl/qa/cppunit/BitmapFilterTest.cxx +++ b/vcl/qa/cppunit/BitmapFilterTest.cxx @@ -119,8 +119,8 @@ void BitmapFilterTest::testBlurCorrectness() } // Check blurred bitmap parameters - CPPUNIT_ASSERT_EQUAL(static_cast(45), aBitmap24Bit.GetSizePixel().Width()); - CPPUNIT_ASSERT_EQUAL(static_cast(35), aBitmap24Bit.GetSizePixel().Height()); + CPPUNIT_ASSERT_EQUAL(static_cast(41), aBitmap24Bit.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(static_cast(31), aBitmap24Bit.GetSizePixel().Height()); CPPUNIT_ASSERT_EQUAL(nBPP, aBitmap24Bit.GetBitCount()); @@ -188,7 +188,7 @@ void BitmapFilterTest::testPerformance() Bitmap aResult; for (int i = 0; i < nIterations; i++) { - BitmapFilterStackBlur aBlurFilter(250, false); // don't extend the image + BitmapFilterStackBlur aBlurFilter(250); aResult = aBlurFilter.filter(aBigBitmap); } auto end = std::chrono::high_resolution_clock::now(); diff --git a/vcl/source/bitmap/BitmapFilterStackBlur.cxx b/vcl/source/bitmap/BitmapFilterStackBlur.cxx index 9b3c37127fcf..da51daedfacc 100644 --- a/vcl/source/bitmap/BitmapFilterStackBlur.cxx +++ b/vcl/source/bitmap/BitmapFilterStackBlur.cxx @@ -558,39 +558,6 @@ void stackBlur8(Bitmap& rBitmap, sal_Int32 nRadius, sal_Int32 nComponentWidth) pBlurVerticalFn, bParallel); } -void centerExtendBitmap(Bitmap& rBitmap, sal_Int32 nExtendSize, Color aColor) -{ - const Size& rSize = rBitmap.GetSizePixel(); - const Size aNewSize(rSize.Width() + nExtendSize * 2, rSize.Height() + nExtendSize * 2); - - Bitmap aNewBitmap(aNewSize, rBitmap.GetBitCount()); - - { - Bitmap::ScopedReadAccess pReadAccess(rBitmap); - BitmapScopedWriteAccess pWriteAccess(aNewBitmap); - - long nWidthBorder = nExtendSize + rSize.Width(); - long nHeightBorder = nExtendSize + rSize.Height(); - - for (long y = 0; y < aNewSize.Height(); y++) - { - for (long x = 0; x < aNewSize.Width(); x++) - { - if (y < nExtendSize || y >= nHeightBorder || x < nExtendSize || x >= nWidthBorder) - { - pWriteAccess->SetPixel(y, x, aColor); - } - else - { - pWriteAccess->SetPixel(y, x, - pReadAccess->GetPixel(y - nExtendSize, x - nExtendSize)); - } - } - } - } - rBitmap = aNewBitmap; -} - } // end anonymous namespace /** @@ -614,9 +581,8 @@ void centerExtendBitmap(Bitmap& rBitmap, sal_Int32 nExtendSize, Color aColor) * (https://code.google.com/p/fog/) * */ -BitmapFilterStackBlur::BitmapFilterStackBlur(sal_Int32 nRadius, bool bExtend) +BitmapFilterStackBlur::BitmapFilterStackBlur(sal_Int32 nRadius) : mnRadius(nRadius) - , mbExtend(bExtend) { } @@ -648,22 +614,12 @@ Bitmap BitmapFilterStackBlur::filter(Bitmap const& rBitmap) const ? 4 : 3; - if (mbExtend) - { - centerExtendBitmap(bitmapCopy, mnRadius, COL_WHITE); - } - stackBlur24(bitmapCopy, mnRadius, nComponentWidth); } else if (nScanlineFormat == ScanlineFormat::N8BitPal) { int nComponentWidth = 1; - if (mbExtend) - { - centerExtendBitmap(bitmapCopy, mnRadius, COL_WHITE); - } - stackBlur8(bitmapCopy, mnRadius, nComponentWidth); }