basegfx/source/tools/gradienttools.cxx                  |   76 +++----
 drawinglayer/inc/texture/texture.hxx                    |    3 
 drawinglayer/source/processor2d/vclpixelprocessor2d.cxx |   14 +
 drawinglayer/source/texture/texture.cxx                 |  173 +++++++++++++---
 include/basegfx/utils/gradienttools.hxx                 |   12 -
 svx/source/sdr/primitive2d/sdrattributecreator.cxx      |   75 ++++++
 6 files changed, 269 insertions(+), 84 deletions(-)

New commits:
commit 808c5bf66370f78f36e98887db0848ee7e55bb3a
Author:     Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de>
AuthorDate: Tue Mar 21 13:13:15 2023 +0100
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Tue Mar 21 15:53:06 2023 +0000

    MCGR: Model data changes for ColorSteps (II)
    
    The biggest change here is to allow multiple ColorStops
    with the same Offset. That allows to define gradients
    with 'hard' color changes at any place using two
    ColorStops with the same Offset and different Colors.
    This required quite some adaptions, but works well.
    
    Also removed in this context checking for all Colors
    being the same to not mix up things. Also works well.
    
    Also changed the need for having Start/EndColors AKA
    ColorStops for 0.0 and 1.0 in place, instead 'imply'
    the 1st ColorStop to also define the StartColor and
    the last one the EndColor. This allows e.g. Gradient
    definitions with two GradientStops at the same
    Offset e.g. 0.5 with different colors to already
    define a full Gradient.
    
    Also added a tooling method to reverse ColorSteps,
    which changes the order and mirrors the Offsets
    (what even keeps an existing sort valid).
    This is useful e.g. for GradientAxial which is the
    only one where for decomposition the Gradient had
    to be interpreted 'reverse' since it's defined
    from center to edge, but for creating correct filled
    polygons to represent this the inverse order had to
    be used, creating polygons from edge to center.
    This led to 'wild' code for this one of six cases
    and prevented unifications with the other cases
    (also made your brain flip).
    Thus I adapted this now to use the reversed
    ColorSteps consequently, and the same principle
    loops than the other implementations to make things
    easier for the future and to use common tooling.
    
    Change-Id: If2943348d17d5b9cd165f4d78f22638a1dff5237
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149208
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>

diff --git a/basegfx/source/tools/gradienttools.cxx 
b/basegfx/source/tools/gradienttools.cxx
index d43f7899121a..6055143d5abd 100644
--- a/basegfx/source/tools/gradienttools.cxx
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -264,6 +264,17 @@ namespace basegfx
 
     namespace utils
     {
+        /* Tooling method to reverse ColorStops, including offsets.
+           When also mirroring offsets a valid sort keeps valid.
+        */
+        void reverseColorStops(ColorStops& rColorStops)
+        {
+            // can use std::reverse, but also need to adapt offset(s)
+            std::reverse(rColorStops.begin(), rColorStops.end());
+            for (auto& candidate : rColorStops)
+                candidate = ColorStop(1.0 - candidate.getStopOffset(), 
candidate.getStopColor());
+        }
+
         /* Tooling method to convert UNO API data to ColorStops
            This will try to extract ColorStop data from the given
            Any, so if it's of type awt::Gradient2 that data will be
@@ -374,13 +385,8 @@ namespace basegfx
              be removed)
            - contains no ColorStops with offset > 1.0 (will
              be removed)
-           - contains no two ColorStops with identical offsets
-             (will be removed, 1st one/smallest offset wins
-             which is also given by sort tooling)
+           - ColorStops with identical offsets are now allowed
            - will be sorted from lowest offset to highest
-           - if all colors are the same, the content will
-             be reduced to a single entry with offset 0.0
-             (force to StartColor)
 
            Some more notes:
            - It can happen that the result is empty
@@ -404,8 +410,15 @@ namespace basegfx
             if (1 == rColorStops.size())
             {
                 // no gradient at all, but preserve given color
-                // and force it to be the StartColor
-                rColorStops[0] = ColorStop(0.0, rColorStops[0].getStopColor());
+                // evtl. correct offset to be in valid range [0.0 .. 1.0]
+                // NOTE: This does not move it to 0.0 or 1.0, it *can* still
+                //       be somewhere in-between what is allowed
+                rColorStops[0] = ColorStop(
+                    std::max(0.0, std::min(1.0, 
rColorStops[0].getStopOffset())),
+                    rColorStops[0].getStopColor());
+
+                // done
+                return;
             }
 
             // start with sorting the input data. Remember that
@@ -414,9 +427,6 @@ namespace basegfx
             std::sort(rColorStops.begin(), rColorStops.end());
 
             // prepare status values
-            bool bSameColorInit(false);
-            bool bAllTheSameColor(true);
-            basegfx::BColor aFirstColor;
             size_t write(0);
 
             // use the paradigm of a band machine with two heads, read
@@ -428,7 +438,7 @@ namespace basegfx
                 // get offset of entry at read position
                 const double rOff(rColorStops[read].getStopOffset());
 
-                // step over < 0 values
+                // step over < 0 values, these are outside and will be removed
                 if (basegfx::fTools::less(rOff, 0.0))
                     continue;
 
@@ -438,23 +448,9 @@ namespace basegfx
                     break;
 
                 // entry is valid value at read position
-
-                // check/init for all-the-same color
-                if(bSameColorInit)
-                {
-                    // already initialized, compare
-                    bAllTheSameColor = bAllTheSameColor && aFirstColor == 
rColorStops[read].getStopColor();
-                }
-                else
-                {
-                    // do initialize, remember 1st valid color
-                    bSameColorInit = true;
-                    aFirstColor = rColorStops[read].getStopColor();
-                }
-
                 // copy if write target is empty (write at start) or when
-                // write target is different to read
-                if (0 == write || rOff != rColorStops[write-1].getStopOffset())
+                // write target is different to read in color or offset
+                if (0 == write || !(rColorStops[read] == rColorStops[write-1]))
                 {
                     if (write != read)
                     {
@@ -473,14 +469,6 @@ namespace basegfx
             {
                 rColorStops.resize(write);
             }
-
-            if (bSameColorInit && bAllTheSameColor && rColorStops.size() > 1)
-            {
-                // id all-the-same color is detected, reset to single
-                // entry, but also force to StartColor and preserve the color
-                rColorStops.resize(1);
-                rColorStops[0] = ColorStop(0.0, aFirstColor);
-            }
         }
 
         BColor modifyBColor(
@@ -493,11 +481,13 @@ namespace basegfx
                 return BColor();
 
             // outside range -> at start
-            if (fScaler <= 0.0)
+            const double fMin(rColorStops.front().getStopOffset());
+            if (fScaler < fMin)
                 return rColorStops.front().getStopColor();
 
             // outside range -> at end
-            if (fScaler >= 1.0)
+            const double fMax(rColorStops.back().getStopOffset());
+            if (fScaler > fMax)
                 return rColorStops.back().getStopColor();
 
             // special case for the 'classic' case with just two colors:
@@ -505,6 +495,9 @@ namespace basegfx
             // by avoiding some calculations and an O(log(N)) array access
             if (2 == rColorStops.size())
             {
+                if (fTools::equal(fMin, fMax))
+                    return rColorStops.front().getStopColor();
+
                 const basegfx::BColor 
aCStart(rColorStops.front().getStopColor());
                 const basegfx::BColor aCEnd(rColorStops.back().getStopColor());
                 const sal_uInt32 nSteps(
@@ -513,6 +506,11 @@ namespace basegfx
                         aCStart,
                         aCEnd));
 
+                // we need to extend the interpolation to the local
+                // range of ColorStops. Despite having two ColorStops
+                // these are not necessarily at 0.0 and 1.0, so mabe
+                // not the classical Start/EndColor (what is allowed)
+                fScaler = (fScaler - fMin) / (fMax - fMin);
                 return basegfx::interpolate(
                     aCStart,
                     aCEnd,
@@ -525,7 +523,7 @@ namespace basegfx
             //       debug test code created by the stl. In a pro version,
             //       all is good/fast as expected
             const auto upperBound(
-                std::lower_bound(
+                std::upper_bound(
                     rColorStops.begin(),
                     rColorStops.end(),
                     ColorStop(fScaler),
diff --git a/drawinglayer/inc/texture/texture.hxx 
b/drawinglayer/inc/texture/texture.hxx
index 8eff5bee261a..8a5c411d320f 100644
--- a/drawinglayer/inc/texture/texture.hxx
+++ b/drawinglayer/inc/texture/texture.hxx
@@ -60,6 +60,9 @@ namespace drawinglayer::texture
             basegfx::ColorStops                 mnColorStops;
             double                              mfBorder;
 
+            // check if we need last-ColorStop-correction
+            bool checkPenultimate();
+
         public:
             GeoTexSvxGradient(
                 const basegfx::B2DRange& rDefinitionRange,
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 1b85cf9d519f..c49deaf3f369 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -940,12 +940,24 @@ void VclPixelProcessor2D::processFillGradientPrimitive2D(
 
     // MCGR: If GradientStops are used, use decomposition since vcl is not able
     // to render multi-color gradients
-    if (rFillGradient.getColorStops().size() > 2)
+    if (rFillGradient.getColorStops().size() != 2)
     {
         process(rPrimitive);
         return;
     }
 
+    // MCGR: If GradientStops do not start and stop at traditional 
Start/EndColor,
+    // use decomposition since vcl is not able to render this
+    if (!rFillGradient.getColorStops().empty())
+    {
+        if 
(!basegfx::fTools::equalZero(rFillGradient.getColorStops().front().getStopOffset())
+            || 
!basegfx::fTools::equal(rFillGradient.getColorStops().back().getStopOffset(), 
1.0))
+        {
+            process(rPrimitive);
+            return;
+        }
+    }
+
     // VCL should be able to handle all styles, but for tdf#133477 the VCL 
result
     // is different from processing the gradient manually by drawinglayer
     // (and the Writer unittest for it fails). Keep using the drawinglayer code
diff --git a/drawinglayer/source/texture/texture.cxx 
b/drawinglayer/source/texture/texture.cxx
index 5cd708243143..136284635e1b 100644
--- a/drawinglayer/source/texture/texture.cxx
+++ b/drawinglayer/source/texture/texture.cxx
@@ -97,6 +97,42 @@ namespace drawinglayer::texture
                 && mfBorder == pCompare->mfBorder);
         }
 
+        bool GeoTexSvxGradient::checkPenultimate()
+        {
+            // not needed when no ColorStops
+            if (mnColorStops.empty())
+                return false;
+
+            // not needed when last ColorStop at the end or outside
+            if 
(basegfx::fTools::moreOrEqual(mnColorStops.back().getStopOffset(), 1.0))
+                return false;
+
+            // get penultimate entry
+            const auto penultimate(mnColorStops.rbegin() + 1);
+
+            // if there is none, we need no correction and are done
+            if (penultimate == mnColorStops.rend())
+                return false;
+
+            // not needed when the last two ColorStops have different offset, 
then
+            // a visible range will be pocessed already
+            if (!basegfx::fTools::equal(mnColorStops.back().getStopOffset(), 
penultimate->getStopOffset()))
+                return false;
+
+            // not needed when the last two ColorStops have the same Color, 
then the
+            // range before solves the problem
+            if (mnColorStops.back().getStopColor() == 
penultimate->getStopColor())
+                return false;
+
+            // Here we need to temporarily add a ColorStop entry with the
+            // same color as the last entry to correctly 'close' the
+            // created gradient geometry.
+            // The simplest way is to temporarily add an entry to the local
+            // ColorStops for this at 1.0 (using same color)
+            mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
+            return true;
+        }
+
         GeoTexSvxGradientLinear::GeoTexSvxGradientLinear(
             const basegfx::B2DRange& rDefinitionRange,
             const basegfx::B2DRange& rOutputRange,
@@ -145,6 +181,9 @@ namespace drawinglayer::texture
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // prepare unit range transform
             basegfx::B2DHomMatrix aPattern;
 
@@ -159,16 +198,22 @@ namespace drawinglayer::texture
             // outer loop over ColorStops, each is from cs_l to cs_r
             for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
+                // calculate StripeWidth
                 // nSteps is >= 1, see getRequestedSteps, so no check needed 
here
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // for the 1st color range we do not need to create the 1st 
step
@@ -206,6 +251,9 @@ namespace drawinglayer::texture
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& 
rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
@@ -237,6 +285,12 @@ namespace drawinglayer::texture
         , mfUnitMinX(0.0)
         , mfUnitWidth(1.0)
         {
+            // ARGH! GradientAxial uses the ColorStops in reverse order 
compared
+            // with the other gradients. Either stay 'thinking reverse' for the
+            // rest of time or adapt it here and go in same order as the other 
five,
+            // so unifications/tooling will be possible
+            basegfx::utils::reverseColorStops(mnColorStops);
+
             maGradientInfo = basegfx::utils::createAxialODFGradientInfo(
                 rDefinitionRange,
                 nRequestedSteps,
@@ -266,13 +320,15 @@ namespace drawinglayer::texture
                 return;
 
             // fill in return parameter rOuterColor before returning
-            // CAUTION: for GradientAxial the color range is inverted (!)
-            rOuterColor = mnColorStops.back().getStopColor();
+            rOuterColor = mnColorStops.front().getStopColor();
 
             // only one color, done
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // prepare unit range transform
             basegfx::B2DHomMatrix aPattern;
 
@@ -285,42 +341,49 @@ namespace drawinglayer::texture
             aPattern.translate(mfUnitMinX, 0.0);
 
             // outer loop over ColorStops, each is from cs_l to cs_r
-            // CAUTION: for GradientAxial the color range is used inverted (!)
-            //          thus, to loop backward, use rbegin/rend
-            for (auto cs_r(mnColorStops.rbegin()), cs_l(cs_r + 1); cs_l != 
mnColorStops.rend(); cs_l++, cs_r++)
+            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
+                // calculate StripeWidth
                 // nSteps is >= 1, see getRequestedSteps, so no check needed 
here
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // for the 1st color range we do not need to create the 1st 
step, see above
-                const sal_uInt32 nStartInnerLoop(cs_r == mnColorStops.rbegin() 
? 1 : 0);
+                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() 
? 1 : 0);
 
                 for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < 
nSteps; innerLoop++)
                 {
                     // calculate pos in Y
-                    const double fPos(fOffsetEnd - (fStripeWidth * innerLoop));
+                    const double fPos(fOffsetStart + (fStripeWidth * 
innerLoop));
 
                     // already centered in Y on X-Axis, just scale in Y
                     basegfx::B2DHomMatrix aNew(aPattern);
-                    aNew.scale(1.0, fPos);
+                    aNew.scale(1.0, 1.0 - fPos);
 
                     // set and add at target
                     B2DHomMatrixAndBColor aB2DHomMatrixAndBColor;
 
                     aB2DHomMatrixAndBColor.maB2DHomMatrix = 
maGradientInfo.getTextureTransform() * aNew;
-                    aB2DHomMatrixAndBColor.maBColor = interpolate(aCEnd, 
aCStart, double(innerLoop) / double(nSteps - 1));
+                    aB2DHomMatrixAndBColor.maBColor = interpolate(aCStart, 
aCEnd, double(innerLoop) / double(nSteps - 1));
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientAxial::modifyBColor(const basegfx::B2DPoint& 
rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
@@ -332,14 +395,16 @@ namespace drawinglayer::texture
             // just single color, done
             if (mnColorStops.size() < 2)
             {
-                // CAUTION: for GradientAxial the color range is used inverted 
(!)
-                rBColor = mnColorStops.back().getStopColor();
+                // we use the reverse ColorSteps here, so use front value
+                rBColor = mnColorStops.front().getStopColor();
                 return;
             }
 
             // texture-back-transform X/Y -> t [0.0..1.0] and determine color
             const double fScaler(basegfx::utils::getAxialGradientAlpha(rUV, 
maGradientInfo));
-            rBColor = basegfx::utils::modifyBColor(mnColorStops, fScaler, 
mnRequestedSteps);
+
+                // we use the reverse ColorSteps here, so mirror scaler value
+            rBColor = basegfx::utils::modifyBColor(mnColorStops, 1.0 - 
fScaler, mnRequestedSteps);
         }
 
 
@@ -378,18 +443,27 @@ namespace drawinglayer::texture
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // outer loop over ColorStops, each is from cs_l to cs_r
             for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
+                // calculate StripeWidth
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // get correct start for inner loop (see above)
@@ -408,6 +482,9 @@ namespace drawinglayer::texture
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientRadial::modifyBColor(const basegfx::B2DPoint& 
rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
@@ -466,6 +543,9 @@ namespace drawinglayer::texture
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // prepare vars dependent on aspect ratio
             const double fAR(maGradientInfo.getAspectRatio());
             const bool bMTO(fAR > 1.0);
@@ -473,15 +553,21 @@ namespace drawinglayer::texture
             // outer loop over ColorStops, each is from cs_l to cs_r
             for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
+                // calculate StripeWidth
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // get correct start for inner loop (see above)
@@ -503,6 +589,9 @@ namespace drawinglayer::texture
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientElliptical::modifyBColor(const 
basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
@@ -561,18 +650,27 @@ namespace drawinglayer::texture
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // outer loop over ColorStops, each is from cs_l to cs_r
             for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
+                // calculate StripeWidth
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // get correct start for inner loop (see above)
@@ -591,6 +689,9 @@ namespace drawinglayer::texture
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientSquare::modifyBColor(const basegfx::B2DPoint& 
rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
@@ -649,6 +750,9 @@ namespace drawinglayer::texture
             if (mnColorStops.size() < 2)
                 return;
 
+            // check if we need last-ColorStop-correction
+            const bool bPenultimateUsed(checkPenultimate());
+
             // prepare vars dependent on aspect ratio
             const double fAR(maGradientInfo.getAspectRatio());
             const bool bMTO(fAR > 1.0);
@@ -656,15 +760,21 @@ namespace drawinglayer::texture
             // outer loop over ColorStops, each is from cs_l to cs_r
             for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != 
mnColorStops.end(); cs_l++, cs_r++)
             {
+                // get offsets
+                const double fOffsetStart(cs_l->getStopOffset());
+                const double fOffsetEnd(cs_r->getStopOffset());
+
+                // same offset, empty ColorStopRange, continue with next step
+                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
+                    continue;
+
                 // get colors & calculate steps
                 const basegfx::BColor aCStart(cs_l->getStopColor());
                 const basegfx::BColor aCEnd(cs_r->getStopColor());
                 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
                     maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
 
-                // get offsets & calculate StripeWidth
-                const double fOffsetStart(cs_l->getStopOffset());
-                const double fOffsetEnd(cs_r->getStopOffset());
+                // calculate StripeWidth
                 const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
                 // get correct start for inner loop (see above)
@@ -686,6 +796,9 @@ namespace drawinglayer::texture
                     rEntries.push_back(aB2DHomMatrixAndBColor);
                 }
             }
+
+            if (bPenultimateUsed)
+                mnColorStops.pop_back();
         }
 
         void GeoTexSvxGradientRect::modifyBColor(const basegfx::B2DPoint& rUV, 
basegfx::BColor& rBColor, double& /*rfOpacity*/) const
diff --git a/include/basegfx/utils/gradienttools.hxx 
b/include/basegfx/utils/gradienttools.hxx
index 85802749478c..33f87717528b 100644
--- a/include/basegfx/utils/gradienttools.hxx
+++ b/include/basegfx/utils/gradienttools.hxx
@@ -196,6 +196,11 @@ namespace basegfx
 
     namespace utils
     {
+        /* Tooling method to reverse ColorStops, including offsets.
+           When also mirroring offsets a valid sort keeps valid.
+        */
+        BASEGFX_DLLPUBLIC void reverseColorStops(ColorStops& rColorStops);
+
         /* Tooling method to convert UNO API data to ColorStops.
            This will try to extract ColorStop data from the given
            Any, so if it's of type awt::Gradient2 that data will be
@@ -238,13 +243,8 @@ namespace basegfx
              be removed)
            - contains no ColorStops with offset > 1.0 (will
              be removed)
-           - contains no two ColorStops with identical offsets
-             (will be removed, 1st one/smallest offset wins
-             which is also given by sort tooling)
+           - ColorStops with identical offsets are now allowed
            - will be sorted from lowest offset to highest
-           - if all colors are the same, the content will
-             be reduced to a single entry with offset 0.0
-             (force to StartColor)
 
            Some more notes:
            - It can happen that the result is empty
diff --git a/svx/source/sdr/primitive2d/sdrattributecreator.cxx 
b/svx/source/sdr/primitive2d/sdrattributecreator.cxx
index 7409c4017202..475a0916f444 100644
--- a/svx/source/sdr/primitive2d/sdrattributecreator.cxx
+++ b/svx/source/sdr/primitive2d/sdrattributecreator.cxx
@@ -494,28 +494,39 @@ namespace drawinglayer::primitive2d
                                 case 2:
                                 {
                                     // single added in-between, no change of 
start/end
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 3:
                                 {
-                                    // check additional StartColor, the one 
given directly has to win
+                                    // check additional StartColor, the second 
one has to win
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 4:
                                 {
-                                    // check additional EndColor, the one 
given directly has to win
-                                    // due this one being added *after* the 
original one
+                                    // check additional EndColor, the first 
one has to win
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(1.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 5:
                                 {
                                     // check invalid color (too low index), 
has to be ignored
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     aColorStops.emplace_back(-1.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
                                     break;
                                 }
@@ -523,6 +534,9 @@ namespace drawinglayer::primitive2d
                                 case 6:
                                 {
                                     // check invalid color (too high index), 
has to be ignored
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     aColorStops.emplace_back(2.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
                                     break;
                                 }
@@ -530,33 +544,45 @@ namespace drawinglayer::primitive2d
                                 case 7:
                                 {
                                     // check in-between single-color section
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(0.3, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
                                     aColorStops.emplace_back(0.7, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 8:
                                 {
                                     // check in-between single-color sections
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(0.2, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
                                     aColorStops.emplace_back(0.4, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
-                                    aColorStops.emplace_back(0.5, 
aXGradient.GetColorStops().front().getStopColor());
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
                                     aColorStops.emplace_back(0.6, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
                                     aColorStops.emplace_back(0.8, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 9:
                                 {
                                     // check single-color start area
-                                    aColorStops.emplace_back(0.6, 
aXGradient.GetColorStops().front().getStopColor());
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(0.6, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
                                 case 10:
                                 {
                                     // check single-color end area
-                                    aColorStops.emplace_back(0.4, 
aXGradient.GetColorStops().back().getStopColor());
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.0, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(0.4, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
+                                    aColorStops.emplace_back(1.0, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 
@@ -564,8 +590,41 @@ namespace drawinglayer::primitive2d
                                 {
                                     // check case without direct Start/EndColor
                                     aColorStops.clear();
-                                    aColorStops.emplace_back(0.4, 
aXGradient.GetColorStops().back().getStopColor());
-                                    aColorStops.emplace_back(0.6, 
aXGradient.GetColorStops().front().getStopColor());
+                                    aColorStops.emplace_back(0.4, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(0.6, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
+                                    break;
+                                }
+
+                                case 12:
+                                {
+                                    // check case without colors at all
+                                    aColorStops.clear();
+                                    break;
+                                }
+
+                                case 13:
+                                {
+                                    // check case with single stop
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    break;
+                                }
+
+                                case 14:
+                                {
+                                    // check case with single-double stop
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    break;
+                                }
+
+                                case 15:
+                                {
+                                    // check case with single stop diff colors
+                                    aColorStops.clear();
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(1.0, 0.0, 0.0)); // red
+                                    aColorStops.emplace_back(0.5, 
basegfx::BColor(0.0, 0.0, 1.0)); // blue
                                     break;
                                 }
 

Reply via email to