From 3cbe3a0259bea4dec70e72191ec3c03441926a07 Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Mon, 14 Jun 2021 15:05:59 +0200 Subject: tdf#101083 speed up SVG rendering with pattern fill By pushing the work down to the vcl layer, which has much more efficient ways of dump lots of copies of a single image Change-Id: Ie83fa56828df91a23b4b29934360ad80d1793c3f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117162 Tested-by: Jenkins Reviewed-by: Noel Grandin --- .../source/primitive2d/patternfillprimitive2d.cxx | 45 ++++++++++++- .../source/processor2d/vclpixelprocessor2d.cxx | 74 ++++++++++++++++++++++ .../source/processor2d/vclpixelprocessor2d.hxx | 2 + 3 files changed, 120 insertions(+), 1 deletion(-) (limited to 'drawinglayer') diff --git a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx index e7a1f7480b6c..45776edc50d5 100644 --- a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx @@ -93,6 +93,30 @@ namespace drawinglayer::primitive2d } } + void PatternFillPrimitive2D::getTileSize( + sal_uInt32& rWidth, + sal_uInt32& rHeight, + const geometry::ViewInformation2D& rViewInformation) const + { + const basegfx::B2DRange aMaskRange(getMask().getB2DRange()); + + // get discrete rounded up square size of a single tile + const basegfx::B2DHomMatrix aMaskRangeTransformation( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + const basegfx::B2DHomMatrix aTransform( + rViewInformation.getObjectToViewTransformation() * aMaskRangeTransformation); + const basegfx::B2DPoint aTopLeft(aTransform * getReferenceRange().getMinimum()); + const basegfx::B2DPoint aX(aTransform * basegfx::B2DPoint(getReferenceRange().getMaxX(), getReferenceRange().getMinY())); + const basegfx::B2DPoint aY(aTransform * basegfx::B2DPoint(getReferenceRange().getMinX(), getReferenceRange().getMaxY())); + const double fW(basegfx::B2DVector(aX - aTopLeft).getLength()); + const double fH(basegfx::B2DVector(aY - aTopLeft).getLength()); + + rWidth = basegfx::fround(ceil(fW)); + rHeight = basegfx::fround(ceil(fH)); + } + Primitive2DContainer PatternFillPrimitive2D::createContent(const geometry::ViewInformation2D& rViewInformation) const { Primitive2DContainer aContent; @@ -142,7 +166,7 @@ namespace drawinglayer::primitive2d // check if content needs to be clipped const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); - const basegfx::B2DRange aContentRange(getChildren().getB2DRange(rViewInformation)); + const basegfx::B2DRange aContentRange(aContent.getB2DRange(rViewInformation)); if(!aUnitRange.isInside(aContentRange)) { @@ -158,6 +182,25 @@ namespace drawinglayer::primitive2d return aContent; } + // create buffered content in given resolution + BitmapEx PatternFillPrimitive2D::createTileImage(sal_uInt32 nWidth, sal_uInt32 nHeight) const + { + const geometry::ViewInformation2D aViewInformation2D; + const Primitive2DContainer aContent(createContent(aViewInformation2D)); + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(nWidth, nHeight), + aContent)); + const primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; + + return convertToBitmapEx( + xEmbedSeq, + aViewInformation2D, + nWidth, + nHeight, + nWidth * nHeight); + } + void PatternFillPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const { Primitive2DContainer aRetval; diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 4e9e963ef065..58f5f2881402 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include #include #include @@ -57,6 +59,7 @@ #include #include #include +#include #include #include @@ -427,6 +430,12 @@ void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitiv static_cast(rCandidate)); break; } + case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D: + { + processPatternFillPrimitive2D( + static_cast(rCandidate)); + break; + } default: { SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( @@ -1231,6 +1240,71 @@ void VclPixelProcessor2D::processFillGradientPrimitive2D( mpOutputDevice->Pop(); } +void VclPixelProcessor2D::processPatternFillPrimitive2D( + const primitive2d::PatternFillPrimitive2D& rPrimitive) +{ + const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange(); + if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0 + || rReferenceRange.getHeight() <= 0.0) + return; + + basegfx::B2DPolyPolygon aMask = rPrimitive.getMask(); + aMask.transform(maCurrentTransformation); + const basegfx::B2DRange aMaskRange(aMask.getB2DRange()); + + if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0) + return; + + sal_uInt32 nTileWidth, nTileHeight; + rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D()); + if (nTileWidth == 0 || nTileHeight == 0) + return; + BitmapEx aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight); + tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange); + + // Unless smooth edges are needed, simply use clipping. + if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing()) + { + mpOutputDevice->Push(PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); + mpOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage)); + mpOutputDevice->Pop(); + return; + } + + impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // if the tile is a single pixel big, just flood fill with that pixel color + if (nTileWidth == 1 && nTileHeight == 1) + { + Color col = aTileImage.GetPixelColor(0, 0); + mpOutputDevice->SetLineColor(col); + mpOutputDevice->SetFillColor(col); + mpOutputDevice->DrawRect(aMaskRect); + } + else + mpOutputDevice->DrawWallpaper(aMaskRect, Wallpaper(aTileImage)); + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // draw mask + VirtualDevice& rMask = aBufferDevice.getTransparence(); + rMask.SetLineColor(); + rMask.SetFillColor(COL_BLACK); + rMask.DrawPolyPolygon(aMask); + + // dump buffer to outdev + aBufferDevice.paint(); +} + } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx index 480fdcaa6e18..eaf212c8e5b1 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx @@ -43,6 +43,7 @@ class GlowPrimitive2D; class ShadowPrimitive2D; class SoftEdgePrimitive2D; class FillGradientPrimitive2D; +class PatternFillPrimitive2D; } namespace drawinglayer::processor2d @@ -101,6 +102,7 @@ class VclPixelProcessor2D final : public VclProcessor2D void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& rCandidate); void processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate); void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive); + void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive); public: /// constructor/destructor -- cgit