basegfx/source/tools/bgradient.cxx                         |  137 ++++++++
 drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx |  188 +++++++++---
 filter/source/svg/svgexport.cxx                            |    3 
 filter/source/svg/svgwriter.cxx                            |  198 +++++++------
 filter/source/svg/svgwriter.hxx                            |   10 
 include/basegfx/utils/bgradient.hxx                        |   17 +
 include/svx/svdxcgv.hxx                                    |    6 
 include/vcl/bitmapex.hxx                                   |    1 
 include/vcl/gdimtf.hxx                                     |    7 
 include/vcl/metaact.hxx                                    |   11 
 oox/source/export/drawingml.cxx                            |   56 ---
 svx/source/svdraw/svdxcgv.cxx                              |    4 
 vcl/source/bitmap/BitmapEx.cxx                             |    5 
 vcl/source/control/fmtfield.cxx                            |    2 
 vcl/source/filter/png/PngImageWriter.cxx                   |    8 
 vcl/source/filter/svm/SvmReader.cxx                        |   21 +
 vcl/source/filter/svm/SvmWriter.cxx                        |   23 +
 vcl/source/gdi/gdimtf.cxx                                  |    9 
 vcl/source/gdi/metaact.cxx                                 |    5 
 19 files changed, 535 insertions(+), 176 deletions(-)

New commits:
commit 9b8c21acd31f08a0c3f8d88ddac57c80ef5997a1
Author:     Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de>
AuthorDate: Mon Jun 5 17:15:34 2023 +0200
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Tue Jun 13 23:20:41 2023 +0200

    MCGR: tdf#155479 repair gradient SVG export for MCGR
    
    Unfortunately SVG export is based on metafiles and thus there
    is (in principle) no way to get the BGradient/ColorStop/MCGR
    data transfered as needed. For that, using UNO API to read the
    model or using B2DPrimitives would help - as is better for the
    export respectively.
    
    Since there is not the time to re-design SVG export I added
    this 'compromize' as a fix. It gets the needed data transported
    over the metafile (that part is the compromize). It then
    exports the MCGR data to SVG (at least - as was already there -
    if it's a linear/axial gradient). This happens now with all
    Gradient Stops when there is a MCGR gradient. That part is/will
    hopefully be re-usable if SVG export  gets redesigned.
    
    I also added a handling for StepCount feature, so when used (in
    LO, others do not have that) 'hard' color stops get generated
    to make the gradient look identical for SVG export.
    
    Had to make adding of that extra-information in metafiles
    dependent on exporting really to SVG. There are 51 cases which
    use 'MetaActionType::COMMENT' which would potentially have
    to be adapted.
    
    Also added code to solve the problem for TransparencePrimitive2D
    at VclMetafileProcessor2D::processTransparencePrimitive2D. This
    will now - also only for SVG export - directly create the needed
    MetaFloatTransparentAction and add additional MCGR information.
    This will be used on SVG export to write a 'Mask' as was done
    before. This is now capable of creating fill MCGR-Masks in
    the sense that any number of TransparencyStops will be supported.
    
    Change-Id: Ic6d022714eae96b8fbc09e60652851ac5799b757
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152623
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>
    (cherry picked from commit a6e72e2b314e64f3199f3eaf1ecf78157446f6dd)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152882
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>

diff --git a/basegfx/source/tools/bgradient.cxx 
b/basegfx/source/tools/bgradient.cxx
index b56ef0540d17..c96cd0b673cb 100644
--- a/basegfx/source/tools/bgradient.cxx
+++ b/basegfx/source/tools/bgradient.cxx
@@ -679,6 +679,117 @@ bool BColorStops::isSymmetrical() const
     return aIter > aRIter;
 }
 
+void BColorStops::doApplyAxial()
+{
+    // preapare new ColorStops
+    basegfx::BColorStops aNewColorStops;
+
+    // add gradient stops in reverse order, scaled to [0.0 .. 0.5]
+    basegfx::BColorStops::const_reverse_iterator aRevCurrColor(rbegin());
+
+    while (aRevCurrColor != rend())
+    {
+        aNewColorStops.emplace_back((1.0 - aRevCurrColor->getStopOffset()) * 
0.5,
+                                    aRevCurrColor->getStopColor());
+        aRevCurrColor++;
+    }
+
+    // prepare non-reverse run
+    basegfx::BColorStops::const_iterator aCurrColor(begin());
+
+    if (basegfx::fTools::equalZero(aCurrColor->getStopOffset()))
+    {
+        // Caution: do not add 1st entry again, that would be double since it 
was
+        // already added as last element of the inverse run above. But only if
+        // the gradient has a start entry for 0.0 aka StartColor, else it is 
correct.
+        aCurrColor++;
+    }
+
+    // add gradient stops in non-reverse order, translated and scaled to [0.5 
.. 1.0]
+    while (aCurrColor != end())
+    {
+        aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 0.5) + 0.5,
+                                    aCurrColor->getStopColor());
+        aCurrColor++;
+    }
+
+    // apply color stops
+    *this = aNewColorStops;
+}
+
+void BColorStops::doApplySteps(sal_uInt16 nStepCount)
+{
+    // check for zero or invalid steps setting -> done
+    if (0 == nStepCount || nStepCount > 100)
+        return;
+
+    // no change needed if single color
+    BColor aSingleColor;
+    if (isSingleColor(aSingleColor))
+        return;
+
+    // prepare new color stops, get L/R iterators for segments
+    basegfx::BColorStops aNewColorStops;
+    basegfx::BColorStops::const_iterator aColorR(begin());
+    basegfx::BColorStops::const_iterator aColorL(aColorR++);
+
+    while (aColorR != end())
+    {
+        // get start/end color for segment
+        const double fStart(aColorL->getStopOffset());
+        const double fDelta(aColorR->getStopOffset() - fStart);
+
+        if (aNewColorStops.empty() || aNewColorStops.back() != *aColorL)
+        {
+            // add start color, but check if it is already there - which is the
+            // case from the 2nd segment on due to a new segment starting with
+            // the same color as the previous one ended
+            aNewColorStops.push_back(*aColorL);
+        }
+
+        if (!basegfx::fTools::equalZero(fDelta))
+        {
+            // create in-between steps, always two at the same positon to
+            // define a 'hard' color stop. Get start/end color for the segment
+            const basegfx::BColor& rStartColor(aColorL->getStopColor());
+            const basegfx::BColor& rEndColor(aColorR->getStopColor());
+
+            if (rStartColor != rEndColor)
+            {
+                // get relative single-step width
+                const double fSingleStep(1.0 / 
static_cast<double>(nStepCount));
+
+                for (sal_uInt16 a(1); a < nStepCount; a++)
+                {
+                    // calculate position since being used twice
+                    const double fPosition(fStart
+                                           + (fDelta * (static_cast<double>(a) 
* fSingleStep)));
+
+                    // add start color of sub-segment
+                    aNewColorStops.emplace_back(
+                        fPosition, basegfx::interpolate(rStartColor, rEndColor,
+                                                        static_cast<double>(a 
- 1) * fSingleStep));
+
+                    // add end color of sub-segment
+                    aNewColorStops.emplace_back(
+                        fPosition, basegfx::interpolate(rStartColor, rEndColor,
+                                                        static_cast<double>(a) 
* fSingleStep));
+                }
+            }
+        }
+
+        // always add end color of segment
+        aNewColorStops.push_back(*aColorR);
+
+        // next segment
+        aColorL++;
+        aColorR++;
+    }
+
+    // apply the change to color stops
+    *this = aNewColorStops;
+}
+
 std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle)
 {
     switch (eStyle)
@@ -1022,6 +1133,32 @@ void BGradient::tryToConvertToAxial()
 
     SetColorStops(aAxialColorStops);
 }
+
+void BGradient::tryToApplyAxial()
+{
+    // only need to do something if css::awt::GradientStyle_AXIAL, else done
+    if (GetGradientStyle() != css::awt::GradientStyle_AXIAL)
+        return;
+
+    // apply the change to color stops
+    aColorStops.doApplyAxial();
+
+    // set style to GradientStyle_LINEAR
+    SetGradientStyle(css::awt::GradientStyle_LINEAR);
+}
+
+void BGradient::tryToApplySteps()
+{
+    // check for zero or invalid steps setting -> done
+    if (0 == GetSteps() || GetSteps() > 100)
+        return;
+
+    // do the action
+    aColorStops.doApplySteps(GetSteps());
+
+    // set value to default
+    SetSteps(0);
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
index 9b536bcbd3d7..feb30d33e1ed 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -66,6 +66,7 @@
 #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for 
Title/Description metadata
 #include <drawinglayer/converters.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <tools/vcompat.hxx>
 
 #include <com/sun/star/awt/XControl.hpp>
 #include <com/sun/star/i18n/BreakIterator.hpp>
@@ -1962,58 +1963,126 @@ void 
VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D(
 void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D(
     const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate)
 {
-    basegfx::B2DVector aScale, aTranslate;
-    double fRotate, fShearX;
+    bool useDecompose(false);
 
-    maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
-
-    if (!basegfx::fTools::equalZero(fRotate) || 
!basegfx::fTools::equalZero(fShearX))
+    if (!useDecompose)
     {
-        // #i121185# When rotation or shear is used, a VCL Gradient cannot be 
used directly.
-        // This is because VCL Gradient mechanism does *not* support to rotate 
the gradient
-        // with objects and this case is not expressible in a Metafile (and 
cannot be added
-        // since the FileFormats used, e.g. *.wmf, do not support it either).
-        // Such cases happen when a graphic object uses a Metafile as graphic 
information or
-        // a fill style definition uses a Metafile. In this cases the graphic 
content is
-        // rotated with the graphic or filled object; this is not supported by 
the target
-        // format of this conversion renderer - Metafiles.
-        // To solve this, not a Gradient is written, but the decomposition of 
this object
-        // is written to the Metafile. This is the PolyPolygons building the 
gradient fill.
-        // These will need more space and time, but the result will be as if 
the Gradient
-        // was rotated with the object.
-        // This mechanism is used by all exporters still not using Primitives 
(e.g. Print,
-        // Slideshow, Export rto PDF, export to Picture, ...) but relying on 
Metafile
-        // transfers. One more reason to *change* these to primitives.
-        // BTW: One more example how useful the principles of primitives are; 
the decomposition
-        // is by definition a simpler, maybe more expensive representation of 
the same content.
-        process(rGradientCandidate);
-        return;
+        basegfx::B2DVector aScale, aTranslate;
+        double fRotate, fShearX;
+
+        maCurrentTransformation.decompose(aScale, aTranslate, fRotate, 
fShearX);
+
+        // detect if transformation is rotated, sheared or mirrored in X 
and/or Y
+        if (!basegfx::fTools::equalZero(fRotate) || 
!basegfx::fTools::equalZero(fShearX)
+            || aScale.getX() < 0.0 || aScale.getY() < 0.0)
+        {
+            // #i121185# When rotation or shear is used, a VCL Gradient cannot 
be used directly.
+            // This is because VCL Gradient mechanism does *not* support to 
rotate the gradient
+            // with objects and this case is not expressible in a Metafile 
(and cannot be added
+            // since the FileFormats used, e.g. *.wmf, do not support it 
either).
+            // Such cases happen when a graphic object uses a Metafile as 
graphic information or
+            // a fill style definition uses a Metafile. In this cases the 
graphic content is
+            // rotated with the graphic or filled object; this is not 
supported by the target
+            // format of this conversion renderer - Metafiles.
+            // To solve this, not a Gradient is written, but the decomposition 
of this object
+            // is written to the Metafile. This is the PolyPolygons building 
the gradient fill.
+            // These will need more space and time, but the result will be as 
if the Gradient
+            // was rotated with the object.
+            // This mechanism is used by all exporters still not using 
Primitives (e.g. Print,
+            // Slideshow, Export rto PDF, export to Picture, ...) but relying 
on Metafile
+            // transfers. One more reason to *change* these to primitives.
+            // BTW: One more example how useful the principles of primitives 
are; the decomposition
+            // is by definition a simpler, maybe more expensive representation 
of the same content.
+            useDecompose = true;
+        }
     }
 
     // tdf#150551 for PDF export, use the decomposition for better gradient 
visualization
-    if (nullptr != mpPDFExtOutDevData)
+    if (!useDecompose && nullptr != mpPDFExtOutDevData)
     {
-        process(rGradientCandidate);
-        return;
+        useDecompose = true;
     }
 
     basegfx::B2DPolyPolygon 
aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon());
 
-    if (aLocalPolyPolygon.getB2DRange() != 
rGradientCandidate.getDefinitionRange())
+    if (!useDecompose && aLocalPolyPolygon.getB2DRange() != 
rGradientCandidate.getDefinitionRange())
     {
         // the range which defines the gradient is different from the range of 
the
         // geometry (used for writer frames). This cannot be done calling vcl, 
thus use
         // decomposition here
-        process(rGradientCandidate);
-        return;
+        useDecompose = true;
     }
 
-    if (!rGradientCandidate.getFillGradient().getColorStops().empty())
+    const attribute::FillGradientAttribute& 
rFillGradient(rGradientCandidate.getFillGradient());
+
+    if (!useDecompose && rFillGradient.cannotBeHandledByVCL())
     {
         // MCGR: if we have ColorStops, do not try to fallback to old 
VCL-Gradient,
         // that will *not* be capable of representing this properly. Use the
         // correct decomposition instead
+        useDecompose = true;
+    }
+
+    if (useDecompose)
+    {
+        GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile());
+
+        // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export
+        if (nullptr != pMetaFile && pMetaFile->getSVG())
+        {
+            // write the color stops to a memory stream
+            SvMemoryStream aMemStm;
+            VersionCompatWrite aCompat(aMemStm, 1);
+
+            const basegfx::BColorStops& 
rColorStops(rFillGradient.getColorStops());
+            sal_uInt16 
nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size()));
+            aMemStm.WriteUInt16(nTmp);
+
+            for (auto const& rCand : rColorStops)
+            {
+                aMemStm.WriteDouble(rCand.getStopOffset());
+                const basegfx::BColor& rColor(rCand.getStopColor());
+                aMemStm.WriteDouble(rColor.getRed());
+                aMemStm.WriteDouble(rColor.getGreen());
+                aMemStm.WriteDouble(rColor.getBlue());
+            }
+
+            // Add a new MetaCommentAction section of type 
'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END'
+            // that is capable of holding the new color step information, plus 
the
+            // already used MetaActionType::GRADIENTEX.
+            // With that combination only places that know about that new 
BGRAD_SEQ_* will
+            // use it while all others will work on the created decomposition 
of the
+            // gradient for compatibility - which are single-color filled 
polygons
+            pMetaFile->AddAction(new MetaCommentAction(
+                "BGRAD_SEQ_BEGIN", 0, static_cast<const 
sal_uInt8*>(aMemStm.GetData()),
+                aMemStm.TellEnd()));
+
+            // create MetaActionType::GRADIENTEX
+            // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon 
and
+            // basegfx::BGradient here directly, but may have to add streaming 
OPs
+            // for these, so for now just go with what we use all the time. 
The real
+            // work for improvement should not go to this 'compromize' but to 
a real
+            // re-work of the SVG export (or/and others) to no longer work on 
metafiles
+            // but on UNO API or primitives (whatever fits best to the 
specific export)
+            fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+            Gradient aVCLGradient;
+            impConvertFillGradientAttributeToVCLGradient(aVCLGradient, 
rFillGradient, false);
+            aLocalPolyPolygon.transform(maCurrentTransformation);
+            const tools::PolyPolygon aToolsPolyPolygon(
+                
getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)));
+            mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient);
+        }
+
+        // use decompose to draw, will create PolyPolygon ColorFill actions
         process(rGradientCandidate);
+
+        // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export
+        if (nullptr != pMetaFile && pMetaFile->getSVG())
+        {
+            // close the BGRAD_SEQ_* actions range
+            pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"));
+        }
+
         return;
     }
 
@@ -2025,8 +2094,7 @@ void 
VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D(
     // it is safest to use the VCL OutputDevice::DrawGradient method which 
creates those.
     // re-create a VCL-gradient from FillGradientPrimitive2D and the needed 
tools PolyPolygon
     Gradient aVCLGradient;
-    impConvertFillGradientAttributeToVCLGradient(aVCLGradient, 
rGradientCandidate.getFillGradient(),
-                                                 false);
+    impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, 
false);
     aLocalPolyPolygon.transform(maCurrentTransformation);
 
     // #i82145# ATM VCL printing of gradients using curved shapes does not 
work,
@@ -2296,6 +2364,10 @@ void 
VclMetafileProcessor2D::processTransparencePrimitive2D(
             pFiGradient = nullptr;
     }
 
+    // tdf#155479 preps for holding extra-MCGR infos
+    bool bSVGTransparencyColorStops(false);
+    basegfx::BColorStops aSVGTransparencyColorStops;
+
     // MCGR: tdf#155437 If we have identified a transparency gradient,
     // check if VCL is able to handle it at all
     if (nullptr != pFiGradient && 
pFiGradient->getFillGradient().cannotBeHandledByVCL())
@@ -2314,7 +2386,21 @@ void 
VclMetafileProcessor2D::processTransparencePrimitive2D(
         // available in metafiles, with the known limitations (not backward 
comp, all
         // places using it would need adaption, ...), but combined with 
knowing that nearly
         // all usages ignore or render it locally anyways makes that a 
non-option.
-        pFiGradient = nullptr;
+
+        // tdf#155479 Yepp, as already mentionmed above we need to add
+        // some MCGR infos in case of SVG export, prepare that here
+        if (nullptr != mpOutputDevice->GetConnectMetaFile()
+            && mpOutputDevice->GetConnectMetaFile()->getSVG())
+        {
+            // for SVG, do not use decompose & prep extra data
+            bSVGTransparencyColorStops = true;
+            aSVGTransparencyColorStops = 
pFiGradient->getFillGradient().getColorStops();
+        }
+        else
+        {
+            // use decomposition
+            pFiGradient = nullptr;
+        }
     }
 
     if (nullptr != pFiGradient)
@@ -2322,6 +2408,13 @@ void 
VclMetafileProcessor2D::processTransparencePrimitive2D(
         // this combination of Gradient can be expressed/handled by
         // vcl/metafile, so add it directly. various content, create 
content-metafile
         GDIMetaFile aContentMetafile;
+
+        // tdf#155479 do not forget to forward SVG flag for sub-content
+        if (bSVGTransparencyColorStops)
+        {
+            aContentMetafile.setSVG(true);
+        }
+
         const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, 
aContentMetafile));
 
         // re-create a VCL-gradient from FillGradientPrimitive2D
@@ -2329,9 +2422,30 @@ void 
VclMetafileProcessor2D::processTransparencePrimitive2D(
         impConvertFillGradientAttributeToVCLGradient(aVCLGradient, 
pFiGradient->getFillGradient(),
                                                      true);
 
-        // render it to VCL (creates MetaFloatTransparentAction)
-        mpOutputDevice->DrawTransparent(aContentMetafile, 
aPrimitiveRectangle.TopLeft(),
-                                        aPrimitiveRectangle.GetSize(), 
aVCLGradient);
+        if (bSVGTransparencyColorStops)
+        {
+            // tdf#155479 create action directly & add extra
+            // MCGR infos to the metafile, do that by adding - ONLY in
+            // case of SVG export - to the MetaFileAction. For that
+            // reason, do what OutputDevice::DrawTransparent will do,
+            // but locally.
+            // NOTE: That would be good for this whole
+            // VclMetafileProcessor2D anyways to allow to get it
+            // completely independent from OutputDevice in the long run
+            GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile());
+            rtl::Reference<::MetaFloatTransparentAction> pAction(
+                new MetaFloatTransparentAction(aContentMetafile, 
aPrimitiveRectangle.TopLeft(),
+                                               aPrimitiveRectangle.GetSize(), 
aVCLGradient));
+
+            pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops);
+            pMetaFile->AddAction(pAction);
+        }
+        else
+        {
+            // render it to VCL (creates MetaFloatTransparentAction)
+            mpOutputDevice->DrawTransparent(aContentMetafile, 
aPrimitiveRectangle.TopLeft(),
+                                            aPrimitiveRectangle.GetSize(), 
aVCLGradient);
+        }
         return;
     }
 
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx
index 776643a9740d..a8afae2c6461 100644
--- a/filter/source/svg/svgexport.cxx
+++ b/filter/source/svg/svgexport.cxx
@@ -2381,7 +2381,8 @@ bool SVGFilter::implCreateObjectsFromShape( const 
Reference< css::drawing::XDraw
 
         if( pObj )
         {
-            Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj));
+            // tdf#155479 need to signal SVG export
+            Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj, true));
 
             // Writer graphic shapes are handled differently
             if( mbWriterFilter && aGraphic.GetType() == GraphicType::NONE )
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
index e5f918afb0aa..bb9f39dd8d19 100644
--- a/filter/source/svg/svgwriter.cxx
+++ b/filter/source/svg/svgwriter.cxx
@@ -39,6 +39,8 @@
 #include <i18nlangtag/languagetag.hxx>
 #include <o3tl/string_view.hxx>
 #include <svx/svdomedia.hxx>
+#include <basegfx/utils/bgradient.hxx>
+#include <tools/vcompat.hxx>
 
 #include <com/sun/star/container/XEnumerationAccess.hpp>
 #include <com/sun/star/container/XIndexReplace.hpp>
@@ -640,7 +642,7 @@ sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& 
rMtf, size_t& nCurAc
                     bEmpty = false;
 
                     mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), 
pA->GetGradient(),
-                                             nWriteFlags, &maTextOpacity);
+                                             nWriteFlags, 
pA->getSVGTransparencyColorStops(), &maTextOpacity);
                 }
             }
             break;
@@ -2313,12 +2315,12 @@ void SVGActionWriter::ImplWritePattern( const 
tools::PolyPolygon& rPolyPoly,
 
 
 void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& 
rPolyPoly, const Gradient& rGradient,
-                                           sal_uInt32 nWriteFlags)
+                                           sal_uInt32 nWriteFlags, const 
basegfx::BColorStops* pColorStops)
 {
     if ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR ||
          rGradient.GetStyle() == css::awt::GradientStyle_AXIAL )
     {
-        ImplWriteGradientLinear( rPolyPoly, rGradient );
+        ImplWriteGradientLinear( rPolyPoly, rGradient, pColorStops );
     }
     else
     {
@@ -2328,7 +2330,7 @@ void SVGActionWriter::ImplWriteGradientEx( const 
tools::PolyPolygon& rPolyPoly,
 
 
 void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& 
rPolyPoly,
-                                               const Gradient& rGradient )
+                                               const Gradient& rGradient, 
const basegfx::BColorStops* pColorStops )
 {
     if( !rPolyPoly.Count() )
         return;
@@ -2371,60 +2373,53 @@ void SVGActionWriter::ImplWriteGradientLinear( const 
tools::PolyPolygon& rPolyPo
 
         {
             SvXMLElementExport aElemLinearGradient( mrExport, 
XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true );
+            basegfx::BColorStops aColorStops;
 
-            const Color aStartColor = ImplGetColorWithIntensity( 
rGradient.GetStartColor(), rGradient.GetStartIntensity() );
-            const Color aEndColor = ImplGetColorWithIntensity( 
rGradient.GetEndColor(), rGradient.GetEndIntensity() );
-            double fBorderOffset = rGradient.GetBorder() / 100.0;
-            const sal_uInt16 nSteps = rGradient.GetSteps();
-            if( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR )
+            if (nullptr != pColorStops && pColorStops->size() > 1)
             {
-                // Emulate non-smooth gradient
-                if( 0 < nSteps && nSteps < 100 )
-                {
-                    double fOffsetStep = ( 1.0 - fBorderOffset ) / 
static_cast<double>(nSteps);
-                    for( sal_uInt16 i = 0; i < nSteps; i++ ) {
-                        Color aColor = ImplGetGradientColor( aStartColor, 
aEndColor, i / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 
) * fOffsetStep );
-                        aColor = ImplGetGradientColor( aStartColor, aEndColor, 
( i + 1 ) / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 
) * fOffsetStep );
-                    }
-                }
-                else
-                {
-                    ImplWriteGradientStop( aStartColor, fBorderOffset );
-                    ImplWriteGradientStop( aEndColor, 1.0 );
-                }
+                // if we got the real colr stops, use them. That way we are
+                // now capable in the SVG export to export real multi color 
gradients
+                aColorStops = *pColorStops;
             }
             else
             {
-                fBorderOffset /= 2;
-                // Emulate non-smooth gradient
-                if( 0 < nSteps && nSteps < 100 )
-                {
-                    double fOffsetStep = ( 0.5 - fBorderOffset ) / 
static_cast<double>(nSteps);
-                    // Upper half
-                    for( sal_uInt16 i = 0; i < nSteps; i++ )
-                    {
-                        Color aColor = ImplGetGradientColor( aEndColor, 
aStartColor, i / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, fBorderOffset + i * 
fOffsetStep );
-                        aColor = ImplGetGradientColor( aEndColor, aStartColor, 
(i + 1 ) / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, fBorderOffset + i * 
fOffsetStep );
-                    }
-                    // Lower half
-                    for( sal_uInt16 i = 0; i < nSteps; i++ )
-                    {
-                        Color aColor = ImplGetGradientColor( aStartColor, 
aEndColor, i / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, 0.5 + (i + 1) * 
fOffsetStep );
-                        aColor = ImplGetGradientColor( aStartColor, aEndColor, 
(i + 1 ) / static_cast<double>(nSteps) );
-                        ImplWriteGradientStop( aColor, 0.5 + (i + 1) * 
fOffsetStep );
-                    }
-                }
-                else
-                {
-                    ImplWriteGradientStop( aEndColor, fBorderOffset );
-                    ImplWriteGradientStop( aStartColor, 0.5 );
-                    ImplWriteGradientStop( aEndColor, 1.0 - fBorderOffset );
-                }
+                // else create color stops with 'old' start/endColor
+                aColorStops.emplace_back(0.0, 
rGradient.GetStartColor().getBColor());
+                aColorStops.emplace_back(1.0, 
rGradient.GetEndColor().getBColor());
+            }
+
+            // create a basegfx::BGradient with the info to be able to directly
+            // use the tooling it offers
+            basegfx::BGradient aGradient(
+                aColorStops,
+                rGradient.GetStyle(),
+                rGradient.GetAngle(),
+                rGradient.GetOfsX(),
+                rGradient.GetOfsY(),
+                rGradient.GetBorder(),
+                rGradient.GetStartIntensity(),
+                rGradient.GetEndIntensity(),
+                rGradient.GetSteps());
+
+            // apply Start/EndIntensity to the whole color stops - if used
+            aGradient.tryToApplyStartEndIntensity();
+
+            // apply border to color stops - if used
+            aGradient.tryToApplyBorder();
+
+            // convert from 'axial' to linear - if needed and used
+            aGradient.tryToApplyAxial();
+
+            // apply 'Steps' as hard gradient stops - if used
+            aGradient.tryToApplySteps();
+
+            // write prepared gradient stops
+            for (const auto& rCand : aGradient.GetColorStops())
+            {
+                ImplWriteGradientStop(
+                    Color(rCand.getStopColor()),
+                    rCand.getStopOffset());
+                    //  aStartColor, fBorderOffset );
             }
         }
     }
@@ -2462,28 +2457,9 @@ Color SVGActionWriter::ImplGetColorWithIntensity( const 
Color& rColor,
 }
 
 
-Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor,
-                                             const Color& rEndColor,
-                                             double fOffset )
-{
-    tools::Long nRedStep = rEndColor.GetRed() - rStartColor.GetRed();
-    tools::Long nNewRed = rStartColor.GetRed() + static_cast<tools::Long>( 
nRedStep * fOffset );
-    nNewRed = ( nNewRed < 0 ) ? 0 : ( nNewRed > 0xFF) ? 0xFF : nNewRed;
-
-    tools::Long nGreenStep = rEndColor.GetGreen() - rStartColor.GetGreen();
-    tools::Long nNewGreen = rStartColor.GetGreen() + static_cast<tools::Long>( 
nGreenStep * fOffset );
-    nNewGreen = ( nNewGreen < 0 ) ? 0 : ( nNewGreen > 0xFF) ? 0xFF : nNewGreen;
-
-    tools::Long nBlueStep = rEndColor.GetBlue() - rStartColor.GetBlue();
-    tools::Long nNewBlue = rStartColor.GetBlue() + static_cast<tools::Long>( 
nBlueStep * fOffset );
-    nNewBlue = ( nNewBlue < 0 ) ? 0 : ( nNewBlue > 0xFF) ? 0xFF : nNewBlue;
-
-    return Color( static_cast<sal_uInt8>(nNewRed), 
static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) );
-}
-
 void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize,
                                 const Gradient& rGradient, sal_uInt32 
nWriteFlags,
-                                OUString* pTextFillOpacity)
+                                const basegfx::BColorStops* pColorStops, 
OUString* pTextFillOpacity)
 {
     OUString aStyle;
     if (rGradient.GetStartColor() == rGradient.GetEndColor())
@@ -2524,7 +2500,19 @@ void SVGActionWriter::StartMask(const Point& rDestPt, 
const Size& rDestSize,
                 aGradient.SetEndColor(aTmpColor);
                 aGradient.SetEndIntensity(nTmpIntensity);
 
-                ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags);
+                // tdf#155479 prep local ColorStops. The code above
+                // implies that the ColorStops need to be reversed,
+                // so do so & use change of local ptr to represent this
+                basegfx::BColorStops aLocalColorStops;
+
+                if (nullptr != pColorStops)
+                {
+                    aLocalColorStops = *pColorStops;
+                    aLocalColorStops.reverseColorStops();
+                    pColorStops = &aLocalColorStops;
+                }
+
+                ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags, 
pColorStops);
             }
         }
 
@@ -2534,7 +2522,7 @@ void SVGActionWriter::StartMask(const Point& rDestPt, 
const Size& rDestSize,
 }
 
 void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, 
const Size& rDestSize,
-                                    const Gradient& rGradient, sal_uInt32 
nWriteFlags)
+                                    const Gradient& rGradient, sal_uInt32 
nWriteFlags, const basegfx::BColorStops* pColorStops)
 {
     Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin());
     const Size aSrcSize(rMtf.GetPrefSize());
@@ -2561,7 +2549,7 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, 
const Point& rDestPt, con
         std::unique_ptr<SvXMLElementExport> pElemG;
         if (!maTextWriter.hasTextOpacity())
         {
-            StartMask(rDestPt, rDestSize, rGradient, nWriteFlags);
+            StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops);
             pElemG.reset(
                 new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, 
aXMLElemG, true, true));
         }
@@ -3284,7 +3272,7 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                     const tools::Polygon aRectPoly( pA->GetRect() );
                     const tools::PolyPolygon aRectPolyPoly( aRectPoly );
 
-                    ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), 
nWriteFlags );
+                    ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), 
nWriteFlags, nullptr );
                 }
             }
             break;
@@ -3294,7 +3282,7 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                 if( nWriteFlags & SVGWRITER_WRITE_FILL )
                 {
                     const MetaGradientExAction* pA = static_cast<const 
MetaGradientExAction*>(pAction);
-                    ImplWriteGradientEx( pA->GetPolyPolygon(), 
pA->GetGradient(), nWriteFlags );
+                    ImplWriteGradientEx( pA->GetPolyPolygon(), 
pA->GetGradient(), nWriteFlags, nullptr );
                 }
             }
             break;
@@ -3341,7 +3329,7 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                     const MetaFloatTransparentAction*   pA = static_cast<const 
MetaFloatTransparentAction*>(pAction);
                     GDIMetaFile                         aTmpMtf( 
pA->GetGDIMetaFile() );
                     ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(),
-                                   pA->GetGradient(), nWriteFlags  );
+                                   pA->GetGradient(), nWriteFlags, 
pA->getSVGTransparencyColorStops()  );
                 }
             }
             break;
@@ -3375,7 +3363,55 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
             {
                 const MetaCommentAction*    pA = static_cast<const 
MetaCommentAction*>(pAction);
 
-                if( ( 
pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) &&
+                if (pA->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_BEGIN"))
+                {
+                    // detect and use the new BGRAD_SEQ_* metafile comment 
actions
+                    const MetaGradientExAction* pGradAction(nullptr);
+                    bool bDone(false);
+
+                    while (!bDone && (++nCurAction < nCount))
+                    {
+                        pAction = rMtf.GetAction(nCurAction);
+
+                        if (MetaActionType::GRADIENTEX == pAction->GetType())
+                        {
+                            // remember the 'paint' data action
+                            pGradAction = static_cast<const 
MetaGradientExAction*>(pAction);
+                        }
+                        else if (MetaActionType::COMMENT == pAction->GetType()
+                            && static_cast<const 
MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_END"))
+                        {
+                            // end action found
+                            bDone = true;
+                        }
+                    }
+
+                    if (nullptr != pGradAction)
+                    {
+                        // we have a complete actions sequence of BGRAD_SEQ_*, 
so we can now
+                        // read the correct color stops here
+                        basegfx::BColorStops aColorStops;
+                        SvMemoryStream aMemStm(const_cast<sal_uInt8 
*>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
+                        VersionCompatRead aCompat(aMemStm);
+                        sal_uInt16 nTmp;
+                        double fOff, fR, fG, fB;
+                        aMemStm.ReadUInt16( nTmp );
+
+                        for (sal_uInt16 a(0); a < nTmp; a++)
+                        {
+                            aMemStm.ReadDouble(fOff);
+                            aMemStm.ReadDouble(fR);
+                            aMemStm.ReadDouble(fG);
+                            aMemStm.ReadDouble(fB);
+
+                            aColorStops.emplace_back(fOff, basegfx::BColor(fR, 
fG, fB));
+                        }
+
+                        // export with real Color Stops
+                        ImplWriteGradientEx(pGradAction->GetPolyPolygon(), 
pGradAction->GetGradient(), nWriteFlags, &aColorStops);
+                    }
+                }
+                else if( ( 
pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) &&
                     ( nWriteFlags & SVGWRITER_WRITE_FILL ) )
                 {
                     const MetaGradientExAction* pGradAction = nullptr;
@@ -3396,7 +3432,7 @@ void SVGActionWriter::ImplWriteActions( const 
GDIMetaFile& rMtf,
                     }
 
                     if( pGradAction )
-                        ImplWriteGradientEx( pGradAction->GetPolyPolygon(), 
pGradAction->GetGradient(), nWriteFlags );
+                        ImplWriteGradientEx( pGradAction->GetPolyPolygon(), 
pGradAction->GetGradient(), nWriteFlags, nullptr );
                 }
                 else if( ( 
pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) &&
                          ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( 
nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) &&
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
index 16ac77a20026..b0f65f319262 100644
--- a/filter/source/svg/svgwriter.hxx
+++ b/filter/source/svg/svgwriter.hxx
@@ -41,6 +41,7 @@
 #include <stack>
 #include <unordered_map>
 
+namespace basegfx { class BColorStops; }
 
 using namespace ::com::sun::star::uno;
 using namespace ::com::sun::star::container;
@@ -340,12 +341,11 @@ private:
     void                    ImplStartClipRegion(sal_Int32 nClipPathId);
     void                    ImplEndClipRegion();
     void                    ImplWriteClipPath( const tools::PolyPolygon& 
rPolyPoly );
-    void                    ImplWriteGradientEx( const tools::PolyPolygon& 
rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags);
-    void                    ImplWriteGradientLinear( const tools::PolyPolygon& 
rPolyPoly, const Gradient& rGradient );
+    void                    ImplWriteGradientEx( const tools::PolyPolygon& 
rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags, const 
basegfx::BColorStops* pColorStops);
+    void                    ImplWriteGradientLinear( const tools::PolyPolygon& 
rPolyPoly, const Gradient& rGradient, const basegfx::BColorStops* pColorStops );
     void                    ImplWriteGradientStop( const Color& rColor, double 
fOffset );
     static Color            ImplGetColorWithIntensity( const Color& rColor, 
sal_uInt16 nIntensity );
-    static Color            ImplGetGradientColor( const Color& rStartColor, 
const Color& rEndColor, double fOffset );
-    void                    ImplWriteMask( GDIMetaFile& rMtf, const Point& 
rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 
nWriteFlags );
+    void                    ImplWriteMask( GDIMetaFile& rMtf, const Point& 
rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 
nWriteFlags, const basegfx::BColorStops* pColorStops);
     void                    ImplWriteText( const Point& rPos, const OUString& 
rText, KernArraySpan pDXArray, tools::Long nWidth );
     void                    ImplWriteText( const Point& rPos, const OUString& 
rText, KernArraySpan pDXArray, tools::Long nWidth, Color aTextColor );
     void                    ImplWriteBmp( const BitmapEx& rBmpEx, const Point& 
rPt, const Size& rSz, const Point& rSrcPt, const Size& rSrcSz, const 
css::uno::Reference<css::drawing::XShape>* pShape);
@@ -377,7 +377,7 @@ public:
 
     void                    SetEmbeddedBitmapRefs( const MetaBitmapActionMap* 
pEmbeddedBitmapsMap );
     void StartMask(const Point& rDestPt, const Size& rDestSize, const 
Gradient& rGradient,
-                   sal_uInt32 nWriteFlags, OUString* pTextStyle = nullptr);
+                   sal_uInt32 nWriteFlags, const basegfx::BColorStops* 
pColorStops, OUString* pTextStyle = nullptr);
     void                    SetPreviewMode(bool bState = true) { mbIsPreview = 
bState; }
 };
 
diff --git a/include/basegfx/utils/bgradient.hxx 
b/include/basegfx/utils/bgradient.hxx
index 1f42b23c6321..3e2acf9a4b67 100644
--- a/include/basegfx/utils/bgradient.hxx
+++ b/include/basegfx/utils/bgradient.hxx
@@ -96,6 +96,8 @@ public:
         return getStopOffset() == rCandidate.getStopOffset()
                && getStopColor() == rCandidate.getStopColor();
     }
+
+    bool operator!=(const BColorStop& rCandidate) const { return !(*this == 
rCandidate); }
 };
 
 /* MCGR: Provide ColorStops definition to the FillGradientAttribute
@@ -276,6 +278,13 @@ public:
 
     // returns true if the color stops are symmetrical in color and offset, 
otherwise false.
     bool isSymmetrical() const;
+    // assume that the color stops represent an Axial gradient
+    // and replace with gradient stops to represent the same
+    // gradient as linear gradient
+    void doApplyAxial();
+
+    // apply Steps as 'hard' color stops
+    void doApplySteps(sal_uInt16 nStepCount);
 };
 
 class BASEGFX_DLLPUBLIC BGradient final
@@ -337,7 +346,11 @@ public:
     /// Tooling method to fill awt::Gradient2 from data contained in the given 
basegfx::BGradient
     css::awt::Gradient2 getAsGradient2() const;
 
-    /// Tooling to handle border correction/integration and StartStopIntensity
+    // Tooling to handle
+    // - border correction/integration
+    // - apply StartStopIntensity to color stops
+    // - convert type from 'axial' to linear
+    // - apply Steps as 'hard' color stops
     void tryToRecreateBorder(basegfx::BColorStops* 
pAssociatedTransparencyStops = nullptr);
     void tryToApplyBorder();
     void tryToApplyStartEndIntensity();
@@ -345,6 +358,8 @@ public:
     // If a linear gradient is symmetrical it is converted to an axial 
gradient.
     // Does nothing in other cases and for other gradient types.
     void tryToConvertToAxial();
+    void tryToApplyAxial();
+    void tryToApplySteps();
 };
 }
 
diff --git a/include/svx/svdxcgv.hxx b/include/svx/svdxcgv.hxx
index 11e16b2ebcdb..2d7f04455913 100644
--- a/include/svx/svdxcgv.hxx
+++ b/include/svx/svdxcgv.hxx
@@ -79,10 +79,14 @@ public:
         The object (can also be a group object) to retrieve a Graphic
         for.
 
+        @param bSVG
+        tdf#155479 need to know it's SVG export, default is false
+
+
         @return a graphical representation of the given object, as it
         appears on screen (e.g. with rotation, if any, applied).
      */
-    static Graphic GetObjGraphic(const SdrObject& rSdrObject);
+    static Graphic GetObjGraphic(const SdrObject& rSdrObject, bool bSVG = 
false);
 
     // The new Draw objects are marked for all paste methods.
     // If bAddMark is true, the new Draw objects are added to an existing
diff --git a/include/vcl/gdimtf.hxx b/include/vcl/gdimtf.hxx
index f8637675482d..d62cd899623b 100644
--- a/include/vcl/gdimtf.hxx
+++ b/include/vcl/gdimtf.hxx
@@ -67,6 +67,9 @@ private:
     bool            m_bRecord;
     bool            m_bUseCanvas;
 
+    // tdf#155479 need to know if it's SVG export
+    bool            m_bSVG;
+
 
     SAL_DLLPRIVATE static Color         ImplColAdjustFnc( const Color& rColor, 
const void* pColParam );
     SAL_DLLPRIVATE static BitmapEx      ImplBmpAdjustFnc( const BitmapEx& 
rBmpEx, const void* pBmpParam );
@@ -186,6 +189,10 @@ public:
     void            UseCanvas( bool _bUseCanvas );
     bool            GetUseCanvas() const { return m_bUseCanvas; }
 
+    // tdf#155479
+    bool getSVG() const { return m_bSVG; }
+    void setSVG(bool bNew) { m_bSVG = bNew; }
+
     /// Dumps the meta actions as XML in metafile.xml.
     void dumpAsXml(const char* pFileName = nullptr) const;
 };
diff --git a/include/vcl/metaact.hxx b/include/vcl/metaact.hxx
index 06357c74a7eb..3f2e85dff6b4 100644
--- a/include/vcl/metaact.hxx
+++ b/include/vcl/metaact.hxx
@@ -43,6 +43,7 @@
 #include <vcl/region.hxx>
 #include <vcl/rendercontext/RasterOp.hxx>
 #include <vcl/wall.hxx>
+#include <basegfx/utils/bgradient.hxx>
 
 #include <memory>
 
@@ -1569,7 +1570,7 @@ public:
     bool                IsTransparent() const override { return true; }
 };
 
-class SAL_DLLPUBLIC_RTTI MetaFloatTransparentAction final : public MetaAction
+class VCL_DLLPUBLIC MetaFloatTransparentAction final : public MetaAction
 {
 private:
 
@@ -1578,6 +1579,9 @@ private:
     Size                maSize;
     Gradient            maGradient;
 
+    // tdf#155479 allow holding MCGR infos
+    std::optional<basegfx::BColorStops> maSVGTransparencyColorStops;
+
 public:
                         MetaFloatTransparentAction();
     MetaFloatTransparentAction(MetaFloatTransparentAction const &) = default;
@@ -1605,6 +1609,11 @@ public:
     void                SetSize(const Size& rSize) { maSize = rSize; }
     void                SetGradient(const Gradient& rGradient) { maGradient = 
rGradient; }
     bool                IsTransparent() const override { return true; }
+
+    // tdf#155479 allow holding MCGR infos
+    const basegfx::BColorStops* getSVGTransparencyColorStops() const
+    { return !maSVGTransparencyColorStops ? nullptr : 
&(*maSVGTransparencyColorStops); }
+    void addSVGTransparencyColorStops(const basegfx::BColorStops& 
rSVGTransparencyColorStops);
 };
 
 class VCL_DLLPUBLIC MetaEPSAction final : public MetaAction
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index bd58cbf21249..5b0772550471 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -761,6 +761,15 @@ void DrawingML::WriteGradientFill(
         basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, 
aSingleAlpha);
     }
 
+    // apply steps if used. Need to do that before synchronizeColorStops
+    // since that may add e.g. for AlphaStops all-the-same no-data entries,
+    // so the number of entries might change
+    if (pGradient->GetSteps())
+    {
+        aColorStops.doApplySteps(pGradient->GetSteps());
+        aAlphaStops.doApplySteps(pGradient->GetSteps());
+    }
+
     // synchronize ColorStops and AlphaStops as preparation to export
     // so also gradients 'coupled' indirectly using the 
'FillTransparenceGradient'
     // method (at import time) will be exported again
@@ -790,48 +799,11 @@ void DrawingML::WriteGradientFill(
         }
         case awt::GradientStyle_AXIAL:
         {
-            // we need to 'double' the gradient to make it appear as what we 
call
-            // 'axial', but also scale and mirror in doing so
-            basegfx::BColorStops aNewColorStops;
-            basegfx::BColorStops aNewAlphaStops;
-
-            // add mirrored gradients, scaled to [0.0 .. 0.5]
-            basegfx::BColorStops::const_reverse_iterator 
aRevCurrColor(aColorStops.rbegin());
-            basegfx::BColorStops::const_reverse_iterator 
aRevCurrAlpha(aAlphaStops.rbegin());
-
-            while (aRevCurrColor != aColorStops.rend() && aRevCurrAlpha != 
aAlphaStops.rend())
-            {
-                aNewColorStops.emplace_back((1.0 - 
aRevCurrColor->getStopOffset()) * 0.5, aRevCurrColor->getStopColor());
-                aNewAlphaStops.emplace_back((1.0 - 
aRevCurrAlpha->getStopOffset()) * 0.5, aRevCurrAlpha->getStopColor());
-                aRevCurrColor++;
-                aRevCurrAlpha++;
-            }
-
-            basegfx::BColorStops::const_iterator 
aCurrColor(aColorStops.begin());
-            basegfx::BColorStops::const_iterator 
aCurrAlpha(aAlphaStops.begin());
-
-            if (basegfx::fTools::equalZero(aCurrColor->getStopOffset()))
-            {
-                // Caution: do not add 1st entry again, that would be double 
since it was
-                // already added as last element of the inverse run above. But 
only if
-                // the gradient has a start entry for 0.0 aka StartColor, else 
it is correct.
-                // Since aColorStops and aAlphaStops are already synched (see
-                // synchronizeColorStops above), testing one of them is 
sufficient here.
-                aCurrColor++;
-                aCurrAlpha++;
-            }
-
-            // add non-mirrored gradients, translated and scaled to [0.5 .. 
1.0]
-            while (aCurrColor != aColorStops.end() && aCurrAlpha != 
aAlphaStops.end())
-            {
-                aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 
0.5) + 0.5, aCurrColor->getStopColor());
-                aNewAlphaStops.emplace_back((aCurrAlpha->getStopOffset() * 
0.5) + 0.5, aCurrAlpha->getStopColor());
-                aCurrColor++;
-                aCurrAlpha++;
-            }
-
-            aColorStops = aNewColorStops;
-            aAlphaStops = aNewAlphaStops;
+            // use tooling to convert from GradientStyle_AXIAL to 
GradientStyle_LINEAR
+            // NOTE: Since aColorStops and aAlphaStops are already synched (see
+            // synchronizeColorStops above) this can be done directly here
+            aColorStops.doApplyAxial();
+            aAlphaStops.doApplyAxial();
 
             // remember being axial
             bAxial = true;
diff --git a/svx/source/svdraw/svdxcgv.cxx b/svx/source/svdraw/svdxcgv.cxx
index bbf100463fc6..050a99707740 100644
--- a/svx/source/svdraw/svdxcgv.cxx
+++ b/svx/source/svdraw/svdxcgv.cxx
@@ -594,7 +594,8 @@ Graphic SdrExchangeView::GetAllMarkedGraphic() const
 }
 
 
-Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject)
+// tdf#155479 bSVG: need to know it's SVG export, default is false
+Graphic SdrExchangeView::GetObjGraphic(const SdrObject& rSdrObject, bool bSVG)
 {
     Graphic aRet;
 
@@ -648,6 +649,7 @@ Graphic SdrExchangeView::GetObjGraphic(const SdrObject& 
rSdrObject)
         pOut->EnableOutput(false);
         pOut->SetMapMode(aMap);
         aMtf.Record(pOut);
+        aMtf.setSVG(bSVG);
         rSdrObject.SingleObjectPainter(*pOut);
         aMtf.Stop();
         aMtf.WindStart();
diff --git a/vcl/source/control/fmtfield.cxx b/vcl/source/control/fmtfield.cxx
index 69497dec2c1b..fc7bdfee6f58 100644
--- a/vcl/source/control/fmtfield.cxx
+++ b/vcl/source/control/fmtfield.cxx
@@ -910,7 +910,7 @@ namespace
             m_rSpinButton.SpinField::SetText(rText, rSel);
         }
 
-        virtual void SetEntryTextColor(const Color* pColor) override
+        virtual void SetEntryTextColor(const ::Color* pColor) override
         {
             if (pColor)
                 m_rSpinButton.SetControlForeground(*pColor);
diff --git a/vcl/source/filter/svm/SvmReader.cxx 
b/vcl/source/filter/svm/SvmReader.cxx
index 5cb9009a5834..4ec9ec0a3d2e 100644
--- a/vcl/source/filter/svm/SvmReader.cxx
+++ b/vcl/source/filter/svm/SvmReader.cxx
@@ -1336,6 +1336,27 @@ rtl::Reference<MetaAction> 
SvmReader::FloatTransparentHandler(ImplMetaReadData*
     pAction->SetSize(aSize);
     pAction->SetGradient(aGradient);
 
+    // tdf#155479 add support for MCGR and SVG export
+    if (aCompat.GetVersion() > 1)
+    {
+        basegfx::BColorStops aColorStops;
+        sal_uInt16 nTmp;
+        double fOff, fR, fG, fB;
+        mrStream.ReadUInt16(nTmp);
+
+        for (sal_uInt16 a(0); a < nTmp; a++)
+        {
+            mrStream.ReadDouble(fOff);
+            mrStream.ReadDouble(fR);
+            mrStream.ReadDouble(fG);
+            mrStream.ReadDouble(fB);
+
+            aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB));
+        }
+
+        pAction->addSVGTransparencyColorStops(aColorStops);
+    }
+
     return pAction;
 }
 
diff --git a/vcl/source/filter/svm/SvmWriter.cxx 
b/vcl/source/filter/svm/SvmWriter.cxx
index c1913b048b27..7eacba78b88e 100644
--- a/vcl/source/filter/svm/SvmWriter.cxx
+++ b/vcl/source/filter/svm/SvmWriter.cxx
@@ -1364,7 +1364,12 @@ void SvmWriter::TransparentHandler(const 
MetaTransparentAction* pAction)
 void SvmWriter::FloatTransparentHandler(const MetaFloatTransparentAction* 
pAction)
 {
     mrStream.WriteUInt16(static_cast<sal_uInt16>(pAction->GetType()));
-    VersionCompatWrite aCompat(mrStream, 1);
+
+    // tdf#155479 prep vars for MCGR
+    const basegfx::BColorStops* 
pSVGTransparencyColorStops(pAction->getSVGTransparencyColorStops());
+    const bool bSVG(nullptr != pSVGTransparencyColorStops);
+
+    VersionCompatWrite aCompat(mrStream, bSVG ? 2 : 1);
 
     SvmWriter aWriter(mrStream);
     GDIMetaFile aMtf = pAction->GetGDIMetaFile();
@@ -1373,6 +1378,22 @@ void SvmWriter::FloatTransparentHandler(const 
MetaFloatTransparentAction* pActio
     aSerializer.writePoint(pAction->GetPoint());
     aSerializer.writeSize(pAction->GetSize());
     aSerializer.writeGradient(pAction->GetGradient());
+
+    // tdf#155479 add support for MCGR and SVG export
+    if (bSVG)
+    {
+        sal_uInt16 
nTmp(sal::static_int_cast<sal_uInt16>(pSVGTransparencyColorStops->size()));
+        mrStream.WriteUInt16(nTmp);
+
+        for (auto const& rCand : *pSVGTransparencyColorStops)
+        {
+            mrStream.WriteDouble(rCand.getStopOffset());
+            const basegfx::BColor& rColor(rCand.getStopColor());
+            mrStream.WriteDouble(rColor.getRed());
+            mrStream.WriteDouble(rColor.getGreen());
+            mrStream.WriteDouble(rColor.getBlue());
+        }
+    }
 }
 
 void SvmWriter::EPSHandler(const MetaEPSAction* pAction)
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
index 6b1300e589d6..e8bb8ba7e0bd 100644
--- a/vcl/source/gdi/gdimtf.cxx
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -117,7 +117,8 @@ GDIMetaFile::GDIMetaFile() :
     m_pOutDev     ( nullptr ),
     m_bPause      ( false ),
     m_bRecord     ( false ),
-    m_bUseCanvas  ( false )
+    m_bUseCanvas  ( false ),
+    m_bSVG        ( false )
 {
 }
 
@@ -130,7 +131,8 @@ GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) :
     m_pOutDev         ( nullptr ),
     m_bPause          ( false ),
     m_bRecord         ( false ),
-    m_bUseCanvas      ( rMtf.m_bUseCanvas )
+    m_bUseCanvas      ( rMtf.m_bUseCanvas ),
+    m_bSVG            ( rMtf.m_bSVG )
 {
     for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i )
     {
@@ -228,6 +230,7 @@ GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& 
rMtf )
         m_bPause = false;
         m_bRecord = false;
         m_bUseCanvas = rMtf.m_bUseCanvas;
+        m_bSVG = rMtf.m_bSVG;
 
         if( rMtf.m_bRecord )
         {
@@ -328,6 +331,7 @@ void GDIMetaFile::Play( GDIMetaFile& rMtf )
     const size_t nObjCount = m_aList.size();
 
     rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas );
+    rMtf.setSVG( rMtf.getSVG() || m_bSVG );
 
     for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; 
nCurPos++ )
     {
@@ -1809,6 +1813,7 @@ void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc 
pFncCol, const void* pCol
     aMtf.m_aPrefSize = m_aPrefSize;
     aMtf.m_aPrefMapMode = m_aPrefMapMode;
     aMtf.m_bUseCanvas = m_bUseCanvas;
+    aMtf.m_bSVG = m_bSVG;
 
     for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() )
     {
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
index 22093a95735e..241940c3b79d 100644
--- a/vcl/source/gdi/metaact.cxx
+++ b/vcl/source/gdi/metaact.cxx
@@ -1908,6 +1908,11 @@ void MetaFloatTransparentAction::Scale( double fScaleX, 
double fScaleY )
     maSize = aRectangle.GetSize();
 }
 
+void MetaFloatTransparentAction::addSVGTransparencyColorStops(const 
basegfx::BColorStops& rSVGTransparencyColorStops)
+{
+    maSVGTransparencyColorStops = rSVGTransparencyColorStops;
+}
+
 MetaEPSAction::MetaEPSAction() :
     MetaAction(MetaActionType::EPS)
 {}
commit e691869486cd4ee4ee1dfb8f7a41440f7f6500fd
Author:     Paris Oplopoios <paris.oplopo...@collabora.com>
AuthorDate: Sat Jun 10 21:33:39 2023 +0300
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Tue Jun 13 23:20:31 2023 +0200

    tdf#154168 Export no transparency PNGs correctly
    
    When bTranslucent was false the function would simply return, causing a
    0 byte png. Now we just remove the alpha channel.
    
    Change-Id: Ie2578185ac12fb38b6f1b674758e564721e3973f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152840
    Tested-by: Jenkins
    Reviewed-by: Paris Oplopoios <parisop...@gmail.com>
    (cherry picked from commit 7588c1f33cdaab58a6b84f4f4e75922c5d4a8a7f)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152886
    Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org>

diff --git a/include/vcl/bitmapex.hxx b/include/vcl/bitmapex.hxx
index 5838ef8d6e40..9d7a104264ef 100644
--- a/include/vcl/bitmapex.hxx
+++ b/include/vcl/bitmapex.hxx
@@ -56,6 +56,7 @@ public:
     bool                IsEmpty() const;
     void                SetEmpty();
     void                Clear();
+    void                ClearAlpha();
 
     void                Draw( OutputDevice* pOutDev,
                               const Point& rDestPt ) const;
diff --git a/vcl/source/bitmap/BitmapEx.cxx b/vcl/source/bitmap/BitmapEx.cxx
index af712a0561a5..ad4adca6319e 100644
--- a/vcl/source/bitmap/BitmapEx.cxx
+++ b/vcl/source/bitmap/BitmapEx.cxx
@@ -190,6 +190,11 @@ void BitmapEx::Clear()
     SetEmpty();
 }
 
+void BitmapEx::ClearAlpha()
+{
+    maAlphaMask.SetEmpty();
+}
+
 bool BitmapEx::IsAlpha() const
 {
     return !maAlphaMask.IsEmpty();
diff --git a/vcl/source/filter/png/PngImageWriter.cxx 
b/vcl/source/filter/png/PngImageWriter.cxx
index 914302223d6a..e68c6155826b 100644
--- a/vcl/source/filter/png/PngImageWriter.cxx
+++ b/vcl/source/filter/png/PngImageWriter.cxx
@@ -60,8 +60,6 @@ static bool pngWrite(SvStream& rStream, const BitmapEx& 
rBitmapEx, int nCompress
                      bool bInterlaced, bool bTranslucent,
                      const std::vector<PngChunk>& aAdditionalChunks)
 {
-    if (rBitmapEx.IsAlpha() && !bTranslucent)
-        return false;
     if (rBitmapEx.IsEmpty())
         return false;
 
@@ -88,6 +86,12 @@ static bool pngWrite(SvStream& rStream, const BitmapEx& 
rBitmapEx, int nCompress
         aBitmapEx = rBitmapEx;
     }
 
+    if (!bTranslucent)
+    {
+        // Clear alpha channel
+        aBitmapEx.ClearAlpha();
+    }
+
     Bitmap aBitmap;
     AlphaMask aAlphaMask;
     Bitmap::ScopedReadAccess pAccess;

Reply via email to