filter/source/svg/svgwriter.cxx |  101 ++++++++++++++++++++++++++--------------
 filter/source/svg/svgwriter.hxx |    3 +
 2 files changed, 70 insertions(+), 34 deletions(-)

New commits:
commit b7a51924b51b3a24891803141c108c21ba540ac5
Author:     Noel Grandin <[email protected]>
AuthorDate: Fri Nov 21 19:26:40 2025 +0200
Commit:     Noel Grandin <[email protected]>
CommitDate: Sat Nov 22 18:18:16 2025 +0100

    tdf#169532 Improve draw/impress export to svg with lots of bitmaps
    
    use the svg <use> element to avoid exporting bitmaps more than once.
    With this document we were exporting the same image hundreds of times.
    
    Change-Id: I9c2501ce67c263720719bac3d801854b48740905
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/194344
    Reviewed-by: Noel Grandin <[email protected]>
    Tested-by: Jenkins

diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index 97578079766c..65e1a38e842f 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -2919,41 +2919,72 @@ void SVGActionWriter::ImplWriteBmp( const Bitmap& rBmp,
 {
     if( rBmp.IsEmpty() )
         return;
-    if( mpEmbeddedBitmapsMap && !mpEmbeddedBitmapsMap->empty())
+    BitmapChecksum nChecksum = rBmp.GetChecksum();
+    if( mpEmbeddedBitmapsMap && mpEmbeddedBitmapsMap->contains( nChecksum ))
     {
-        BitmapChecksum nChecksum = rBmp.GetChecksum();
-        if( mpEmbeddedBitmapsMap->contains( nChecksum ) )
-        {
-            // <use transform="translate(?) scale(?)" xlink:ref="?" >
-            OUString sTransform;
-
-            Point aPoint;
-            ImplMap( rPt, aPoint );
-            if( aPoint.X() != 0 || aPoint.Y() != 0 )
-                sTransform = "translate(" + OUString::number( aPoint.X() ) + 
", " + OUString::number( aPoint.Y() ) + ")";
-
-            Size  aSize;
-            ImplMap( rSz, aSize );
-
-            MapMode aSourceMode( MapUnit::MapPixel );
-            Size aPrefSize = OutputDevice::LogicToLogic( rSrcSz, aSourceMode, 
maTargetMapMode );
-            Fraction aFractionX( aSize.Width(), aPrefSize.Width() );
-            Fraction aFractionY( aSize.Height(), aPrefSize.Height() );
-            double scaleX = rtl_math_round( double(aFractionX), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
-            double scaleY = rtl_math_round( double(aFractionY), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
-            if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( 
scaleY, 1.0 ) )
-                sTransform += " scale(" + OUString::number( double(aFractionX) 
) + ", " + OUString::number( double(aFractionY) ) + ")";
-
-            if( !sTransform.isEmpty() )
-                mrExport.AddAttribute(aXMLAttrTransform, sTransform);
-
-            // referenced bitmap template
-            OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
-            mrExport.AddAttribute(aXMLAttrXLinkHRef, sRefId);
-
-            SvXMLElementExport aRefElem(mrExport, u"use"_ustr, true, true);
-            return;
-        }
+        // <use transform="translate(?) scale(?)" xlink:ref="?" >
+        OUString sTransform;
+
+        Point aPoint;
+        ImplMap( rPt, aPoint );
+        if( aPoint.X() != 0 || aPoint.Y() != 0 )
+            sTransform = "translate(" + OUString::number( aPoint.X() ) + ", " 
+ OUString::number( aPoint.Y() ) + ")";
+
+        Size  aSize;
+        ImplMap( rSz, aSize );
+
+        MapMode aSourceMode( MapUnit::MapPixel );
+        Size aPrefSize = OutputDevice::LogicToLogic( rSrcSz, aSourceMode, 
maTargetMapMode );
+        Fraction aFractionX( aSize.Width(), aPrefSize.Width() );
+        Fraction aFractionY( aSize.Height(), aPrefSize.Height() );
+        double scaleX = rtl_math_round( double(aFractionX), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+        double scaleY = rtl_math_round( double(aFractionY), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+        if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( 
scaleY, 1.0 ) )
+            sTransform += " scale(" + OUString::number( double(aFractionX) ) + 
", " + OUString::number( double(aFractionY) ) + ")";
+
+        if( !sTransform.isEmpty() )
+            mrExport.AddAttribute(aXMLAttrTransform, sTransform);
+
+        // referenced bitmap template
+        OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
+        mrExport.AddAttribute(aXMLAttrXLinkHRef, sRefId);
+
+        SvXMLElementExport aRefElem(mrExport, u"use"_ustr, true, true);
+        return;
+    }
+
+    // check if we can re-use another element to avoid exporting this bitmap 
more than once
+    if( maBitmapElements.contains( nChecksum ))
+    {
+        const tools::Rectangle & rSourceRect = maBitmapElements[nChecksum];
+
+        // <use transform="translate(?) scale(?)" xlink:ref="?" >
+        OUString sTransform;
+
+        Point aPoint;
+        ImplMap( rPt, aPoint );
+        if( aPoint.X() != 0 || aPoint.Y() != 0 )
+            sTransform = "translate(" + OUString::number( 
rSourceRect.TopLeft().X() - aPoint.X() )
+                        + ", " + OUString::number( rSourceRect.TopLeft().Y() - 
aPoint.Y() ) + ")";
+
+        Size  aSize;
+        ImplMap( rSz, aSize );
+
+        Fraction aFractionX( aSize.Width(), rSourceRect.GetWidth() );
+        Fraction aFractionY( aSize.Height(), rSourceRect.GetHeight() );
+        double scaleX = rtl_math_round( double(aFractionX), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+        double scaleY = rtl_math_round( double(aFractionY), 3, 
rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+        if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( 
scaleY, 1.0 ) )
+            sTransform += " scale(" + OUString::number( double(aFractionX) ) + 
", " + OUString::number( double(aFractionY) ) + ")";
+
+        if( !sTransform.isEmpty() )
+            mrExport.AddAttribute(aXMLAttrTransform, sTransform);
+
+        // referenced bitmap template
+        mrExport.AddAttribute(aXMLAttrXLinkHRef, "#bitmap(" + 
OUString::number( nChecksum ) + ")");
+
+        SvXMLElementExport aRefElem(mrExport, u"use"_ustr, true, true);
+        return;
     }
 
     Bitmap aBmp( rBmp );
@@ -3043,6 +3074,8 @@ void SVGActionWriter::ImplWriteBmp( const Bitmap& rBmp,
     mrExport.AddAttribute(aXMLAttrWidth, OUString::number(aSz.Width()));
     mrExport.AddAttribute(aXMLAttrHeight, OUString::number(aSz.Height()));
 
+    maBitmapElements[nChecksum] = tools::Rectangle(aPt, aSz);
+
     // If we have a media object (a video), export the video.
     // Also, use the image generated above as the video poster (thumbnail).
     SdrMediaObj* pMediaObj
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
index 1ddd34ee7603..8b9b531caa38 100644
--- a/filter/source/svg/svgwriter.hxx
+++ b/filter/source/svg/svgwriter.hxx
@@ -314,6 +314,9 @@ private:
     bool                                        mbIsPlaceholderShape;
     const MetaBitmapActionMap*                  mpEmbeddedBitmapsMap;
     bool                                        mbIsPreview;
+    /// Map of the bitmap we have exported via elements with embedded "data:" 
URIs.
+    /// So that we can re-use them.
+    std::unordered_map<BitmapChecksum, tools::Rectangle> maBitmapElements;
 
 
     tools::Long                    ImplMap( sal_Int32 nVal ) const;

Reply via email to