basegfx/source/tools/gradienttools.cxx                  |  101 +++++++++++++++-
 drawinglayer/source/attribute/fillgradientattribute.cxx |   40 ++++--
 drawinglayer/source/texture/texture.cxx                 |   95 +++++++++------
 include/basegfx/utils/gradienttools.hxx                 |   23 +++
 svx/source/sdr/primitive2d/sdrattributecreator.cxx      |   94 +++++++++++++-
 5 files changed, 292 insertions(+), 61 deletions(-)

New commits:
commit c4f252035cd71f9a1b226e69095cc9f4e8f19f3e
Author:     Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de>
AuthorDate: Tue Feb 21 18:35:39 2023 +0100
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Wed Feb 22 09:13:20 2023 +0000

    MCGR: Adapted GradientLinear to make use of MCGR
    
    Added to make GradientLinear work using the MCGR
    as 1st of six types. Had to do quite some tickeling
    to get it all work, but looks good.
    
    Five more to go, already started to put some things
    to tooling to make re-usable for the other types.
    
    Besides adapting this the main change is that the
    adaption of defined step-count (versus automatic)
    has to be done in modifyBColor now instead of the
    back-mapping methods (e.g. getLinearGradientAlpha).
    
    It is still 100% backward-compatible, so as long
    as there is no source using this it will stay
    invisible - by purpose.
    
    I started to do quite some tests (and fixes/
    adaptions in consequence), see the static variable
    nUseGradientSteps. If you want to play with this,
    you might set it to '1' instead of '0' and use a
    linear gradient on an object.
    
    Change-Id: I9d61934defb0674456247f2879f0a89b6a5e50f7
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147413
    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 b2635b3f09cc..43cca3f1c7ea 100644
--- a/basegfx/source/tools/gradienttools.cxx
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -21,6 +21,8 @@
 #include <basegfx/point/b2dpoint.hxx>
 #include <basegfx/range/b2drange.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <algorithm>
 #include <cmath>
 
 namespace basegfx
@@ -261,6 +263,95 @@ namespace basegfx
 
     namespace utils
     {
+        BColor modifyBColor(
+            const ColorSteps& rColorSteps,
+            double fScaler,
+            sal_uInt32 nRequestedSteps)
+        {
+            // no color at all, done
+            if (rColorSteps.empty())
+                return BColor();
+
+            // outside range -> at start
+            if (fScaler <= 0.0)
+                return rColorSteps.front().getColor();
+
+            // outside range -> at end
+            if (fScaler >= 1.0)
+                return rColorSteps.back().getColor();
+
+            // special case for the 'classic' case with just two colors:
+            // we can optimize that and keep the speed/ressources low
+            // by avoiding some calculatins and an O(log(N)) array access
+            if (2 == rColorSteps.size())
+            {
+                const basegfx::BColor aCStart(rColorSteps.front().getColor());
+                const basegfx::BColor aCEnd(rColorSteps.back().getColor());
+                const sal_uInt32 nSteps(
+                    calculateNumberOfSteps(
+                        nRequestedSteps,
+                        aCStart,
+                        aCEnd));
+
+                return basegfx::interpolate(
+                    aCStart,
+                    aCEnd,
+                    nSteps > 1 ? floor(fScaler * nSteps) / double(nSteps - 1) 
: fScaler);
+            }
+
+            // access needed spot in sorted array using binary search
+            // NOTE: This *seems* slow(er) when developing compared to just
+            //       looping/accessing, but that's just due to the extensive
+            //       debug test code created by the stl. In a pro version,
+            //       all is good/fast as expected
+            const auto upperBound(
+                std::lower_bound(
+                    rColorSteps.begin(),
+                    rColorSteps.end(),
+                    ColorStep(fScaler),
+                    [](const ColorStep& x, const ColorStep& y) { return 
x.getOffset() < y.getOffset(); }));
+
+            // no upper bound, done
+            if (rColorSteps.end() == upperBound)
+                return rColorSteps.back().getColor();
+
+            // lower bound is one entry back
+            const auto lowerBound(upperBound - 1);
+
+            // no lower bound, done
+            if (rColorSteps.end() == lowerBound)
+                return rColorSteps.back().getColor();
+
+            // we have lower and upper bound, get colors
+            const BColor aCStart(lowerBound->getColor());
+            const BColor aCEnd(upperBound->getColor());
+
+            // when there are just two color steps this cannot happen, but 
when using
+            // a range of colors this *may* be used inside the range to 
represent
+            // single-colored regions inside a ColorRange. Use that color & 
done
+            if (aCStart == aCEnd)
+                return aCStart;
+
+            // calculate number of steps
+            const sal_uInt32 nSteps(
+                calculateNumberOfSteps(
+                    nRequestedSteps,
+                    aCStart,
+                    aCEnd));
+
+            // get offsets and scale to new [0.0 .. 1.0] relative range for
+            // partial outer range
+            const double fOffsetStart(lowerBound->getOffset());
+            const double fOffsetEnd(upperBound->getOffset());
+            const double fAdaptedScaler((fScaler - fOffsetStart) / (fOffsetEnd 
- fOffsetStart));
+
+            // interpolate & evtl. apply steps
+            return interpolate(
+                aCStart,
+                aCEnd,
+                nSteps > 1 ? floor(fAdaptedScaler * nSteps) / double(nSteps - 
1) : fAdaptedScaler);
+        }
+
         sal_uInt32 calculateNumberOfSteps(
             sal_uInt32 nRequestedSteps,
             const BColor& rStart,
@@ -392,12 +483,12 @@ namespace basegfx
                 return 1.0; // end value for outside
             }
 
-            const sal_uInt32 nSteps(rGradInfo.getRequestedSteps());
+            // const sal_uInt32 nSteps(rGradInfo.getRequestedSteps());
 
-            if(nSteps)
-            {
-                return floor(aCoor.getY() * nSteps) / double(nSteps - 1);
-            }
+            // if(nSteps)
+            // {
+            //     return floor(aCoor.getY() * nSteps) / double(nSteps - 1);
+            // }
 
             return aCoor.getY();
         }
diff --git a/drawinglayer/source/attribute/fillgradientattribute.cxx 
b/drawinglayer/source/attribute/fillgradientattribute.cxx
index c653a6e84057..2056d58acf7f 100644
--- a/drawinglayer/source/attribute/fillgradientattribute.cxx
+++ b/drawinglayer/source/attribute/fillgradientattribute.cxx
@@ -59,32 +59,45 @@ namespace drawinglayer::attribute
                 // if we have given ColorSteps, integrate these
                 if(nullptr != pColorSteps && !pColorSteps->empty())
                 {
-                    // append early & sort to local to prepare processing and 
correction(s)
+                    // append early to local & sort to prepare the following 
correction(s)
+                    // and later processing
                     maColorSteps.insert(maColorSteps.end(), 
pColorSteps->begin(), pColorSteps->end());
-                    std::sort(maColorSteps.begin(), maColorSteps.end());
+
+                    // no need to sort knowingly lowest entry StartColor, also 
guarantees that
+                    // entry to stay at begin
+                    std::sort(maColorSteps.begin() + 1, maColorSteps.end());
 
                     // use two r/w heads on the data band maColorSteps
                     size_t curr(0), next(1);
 
-                    // check if all colors are the same. We know the 
StartColor, so
-                    // to all be the same all have to be equal to StartColor, 
including
-                    // EndColor
+                    // during procesing, check if all colors are the same. We 
know the
+                    // StartColor, so to all be the same, all also have to be 
equal to
+                    // StartColor (including EndColor, use to initialize)
                     bool bAllTheSameColor(rStartColor == rEndColor);
 
-                    // remove entries < 0.0, > 1.0 and with equal offset. do
+                    // remove entries <= 0.0, >= 1.0 and with equal offset. do
                     // this inside the already sorted local vector by evtl.
-                    // moving entries towards begin to keep and adapting size
+                    // moving entries closer to begin to keep and adapting size
                     // at the end
                     for(; next < maColorSteps.size(); next++)
                     {
                         const double 
fNextOffset(maColorSteps[next].getOffset());
 
-                        if(basegfx::fTools::less(fNextOffset, 0.0))
+                        // check for < 0.0 (should not really happen, see 
::ColorStep)
+                        // also check for == 0.0 which would mean than an 
implicit
+                        // StartColor was given in ColorSteps - ignore that, 
we want
+                        // the explicitely given StartColor to always win
+                        if(basegfx::fTools::lessOrEqual(fNextOffset, 0.0))
                             continue;
 
-                        if(basegfx::fTools::more(fNextOffset, 1.0))
+                        // check for > 1.0 (should not really happen, see 
::ColorStep)
+                        // also check for == 1.0 which would mean than an 
implicit
+                        // EndColor was given in ColorSteps - ignore that, we 
want
+                        // the explicitely given EndColor to always win
+                        if(basegfx::fTools::moreOrEqual(fNextOffset, 1.0))
                             continue;
 
+                        // check for equal current offset
                         const double 
fCurrOffset(maColorSteps[curr].getOffset());
                         if(basegfx::fTools::equal(fNextOffset, fCurrOffset))
                             continue;
@@ -97,18 +110,18 @@ namespace drawinglayer::attribute
                             maColorSteps[curr] = maColorSteps[next];
                         }
 
-                        // new entry added, check it for all the same color
+                        // new valid entry detected, check it for all the same 
color
                         bAllTheSameColor = bAllTheSameColor && 
maColorSteps[curr].getColor() == rStartColor;
                     }
 
                     if(bAllTheSameColor)
                     {
-                        // if all the same, reset to StartColor only
+                        // if all are the same color, reset to StartColor only
                         maColorSteps.resize(1);
                     }
                     else
                     {
-                        // adapt size to useful entries
+                        // adapt size to detected useful entries
                         curr++;
                         if(curr != maColorSteps.size())
                         {
@@ -116,7 +129,8 @@ namespace drawinglayer::attribute
                         }
 
                         // add EndColor if in-between colors were added
-                        if(curr > 1)
+                        // or StartColor != EndColor
+                        if(curr > 1 || rStartColor != rEndColor)
                         {
                             maColorSteps.emplace_back(1.0, rEndColor);
                         }
diff --git a/drawinglayer/source/texture/texture.cxx 
b/drawinglayer/source/texture/texture.cxx
index 623336187495..326d3bbe9146 100644
--- a/drawinglayer/source/texture/texture.cxx
+++ b/drawinglayer/source/texture/texture.cxx
@@ -134,17 +134,23 @@ namespace drawinglayer::texture
             std::vector< B2DHomMatrixAndBColor >& rEntries,
             basegfx::BColor& rOuterColor)
         {
-            if(mnColorSteps.size() <= 1)
+            // no color at all, done
+            if (mnColorSteps.empty())
                 return;
 
-            const basegfx::BColor maStart(mnColorSteps.front().getColor());
-            const basegfx::BColor maEnd(mnColorSteps.back().getColor());
-            const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
-                maGradientInfo.getRequestedSteps(), maStart, maEnd));
+            // get start color and also cc to rOuterColor
+            basegfx::BColor aCStart(mnColorSteps[0].getColor());
+            rOuterColor = aCStart;
 
-            rOuterColor = maStart;
-            const double fStripeWidth(1.0 / nSteps);
-            B2DHomMatrixAndBColor aB2DHomMatrixAndBColor;
+            // only one color, done
+            if (mnColorSteps.size() < 2)
+                return;
+
+            // here we could check that fOffsetStart == 
mnColorSteps[0].getOffset(),
+            // but just assume/correct that StartColor is at 0.0
+            double fOffsetStart(0.0 /*mnColorSteps[0].getOffset()*/);
+
+            // prepare unit range transform
             basegfx::B2DHomMatrix aPattern;
 
             // bring from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1]
@@ -155,47 +161,72 @@ namespace drawinglayer::texture
             aPattern.scale(mfUnitWidth, 1.0);
             aPattern.translate(mfUnitMinX, 0.0);
 
-            for(sal_uInt32 a(1); a < nSteps; a++)
+            for (size_t outerLoop(1); outerLoop < mnColorSteps.size(); 
outerLoop++)
             {
-                const double fPos(fStripeWidth * a);
-                basegfx::B2DHomMatrix aNew(aPattern);
+                const basegfx::BColor 
aCEnd(mnColorSteps[outerLoop].getColor());
+                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
+                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
+                const double fOffsetEnd(mnColorSteps[outerLoop].getOffset());
 
-                // scale and translate in Y
-                double fHeight(1.0 - fPos);
+                // nSteps is >= 1, see getRequestedSteps, so no check needed 
here
+                const double fStripeWidth((fOffsetEnd - fOffsetStart) / 
nSteps);
 
-                if(a + 1 == nSteps && mfUnitMaxY > 1.0)
+                // for the 1st color range we do not need to create the 1st 
step
+                // since it will be equal to StartColor and thus rOuterColor, 
so
+                // will be painted by the 1st, always-created background 
polygon
+                // colored using rOuterColor.
+                // We *need* to create this though for all 'inner' color ranges
+                // to get a correct start
+                const sal_uInt32 nStartInnerLoop(1 == outerLoop ? 1 : 0);
+
+                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < 
nSteps; innerLoop++)
                 {
-                    fHeight += mfUnitMaxY - 1.0;
-                }
+                    // calculate pos in Y
+                    const double fPos(fOffsetStart + (fStripeWidth * 
innerLoop));
 
-                aNew.scale(1.0, fHeight);
-                aNew.translate(0.0, fPos);
+                    // scale and translate in Y. For GradientLinear we always 
have
+                    // the full height
+                    double fHeight(1.0 - fPos);
 
-                // set at target
-                aB2DHomMatrixAndBColor.maB2DHomMatrix = 
maGradientInfo.getTextureTransform() * aNew;
+                    if (mfUnitMaxY > 1.0)
+                    {
+                        // extend when difference between definition and 
OutputRange exists
+                        fHeight += mfUnitMaxY - 1.0;
+                    }
 
-                // interpolate and set color
-                aB2DHomMatrixAndBColor.maBColor = interpolate(maStart, maEnd, 
double(a) / double(nSteps - 1));
+                    basegfx::B2DHomMatrix aNew(aPattern);
+                    aNew.scale(1.0, fHeight);
+                    aNew.translate(0.0, fPos);
 
-                rEntries.push_back(aB2DHomMatrixAndBColor);
+                    // set and add at target
+                    B2DHomMatrixAndBColor aB2DHomMatrixAndBColor;
+
+                    aB2DHomMatrixAndBColor.maB2DHomMatrix = 
maGradientInfo.getTextureTransform() * aNew;
+                    aB2DHomMatrixAndBColor.maBColor = interpolate(aCStart, 
aCEnd, double(innerLoop) / double(nSteps - 1));
+                    rEntries.push_back(aB2DHomMatrixAndBColor);
+                }
+
+                aCStart = aCEnd;
+                fOffsetStart = fOffsetEnd;
             }
         }
 
         void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& 
rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
         {
-            if(mnColorSteps.size() <= 1)
+            // no color at all, done
+            if (mnColorSteps.empty())
+                return;
+
+            // just single color, done
+            if (mnColorSteps.size() < 2)
             {
                 rBColor = mnColorSteps.front().getColor();
+                return;
             }
-            else
-            {
-                const double 
fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo));
-
-                const basegfx::BColor maStart(mnColorSteps.front().getColor());
-                const basegfx::BColor maEnd(mnColorSteps.back().getColor());
 
-                rBColor = basegfx::interpolate(maStart, maEnd, fScaler);
-            }
+            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
+            const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, 
maGradientInfo));
+            rBColor = basegfx::utils::modifyBColor(mnColorSteps, fScaler, 
mnRequestedSteps);
         }
 
         GeoTexSvxGradientAxial::GeoTexSvxGradientAxial(
diff --git a/include/basegfx/utils/gradienttools.hxx 
b/include/basegfx/utils/gradienttools.hxx
index 289fc730b790..793d07a75577 100644
--- a/include/basegfx/utils/gradienttools.hxx
+++ b/include/basegfx/utils/gradienttools.hxx
@@ -58,14 +58,18 @@ namespace basegfx
     class UNLESS_MERGELIBS(BASEGFX_DLLPUBLIC) ColorStep
     {
     private:
+        // offset in the range of [0.0 .. 1.0], checked & force by constructor
         double mfOffset;
+
+        // color of ColorStep entry
         BColor maColor;
 
     public:
         // constructor - defaults are needed to have a default constructor
         // e.g. for usage in std::vector::insert
+        // ensure [0.0 .. 1.0] range for mfOffset
         ColorStep(double fOffset = 0.0, const BColor& rColor = BColor())
-            : mfOffset(fOffset)
+            : mfOffset(std::max(0.0, std::min(fOffset, 1.0)))
             , maColor(rColor)
         {
         }
@@ -86,9 +90,10 @@ namespace basegfx
 
     /* MCGR: Provide ColorSteps definition to the FillGradientAttribute
 
-        This array is sorted ascending by offsets, from lowest to
-        highest. Since all this primitive data definition is read-only,
-        this can be guaranteed by forcing/checking this in the constructor.
+        This array should be sorted ascending by offsets, from lowest to
+        highest. Since all the primitive data definition where it is used
+        is read-only, this can/will be guaranteed by forcing/checking this
+        in the constructor, see ::FillGradientAttribute
     */
     typedef std::vector<ColorStep> ColorSteps;
 
@@ -179,6 +184,16 @@ namespace basegfx
 
     namespace utils
     {
+        /* Helper to grep the correct ColorStep out of
+           ColorSteps and interpolate as needed for given
+           relative value in fScaler in the range of [0.0 .. 1.0].
+           It also takes care of evtl. given RequestedSteps.
+        */
+        BASEGFX_DLLPUBLIC BColor modifyBColor(
+            const ColorSteps& rColorSteps,
+            double fScaler,
+            sal_uInt32 nRequestedSteps);
+
         /* Helper to calculate numberOfSteps needed to represent
            gradient for the given two colors:
            - to define only based on color distance, give 0 == nRequestedSteps
diff --git a/svx/source/sdr/primitive2d/sdrattributecreator.cxx 
b/svx/source/sdr/primitive2d/sdrattributecreator.cxx
index 434be3ee3197..048d5430f6f8 100644
--- a/svx/source/sdr/primitive2d/sdrattributecreator.cxx
+++ b/svx/source/sdr/primitive2d/sdrattributecreator.cxx
@@ -495,13 +495,93 @@ namespace drawinglayer::primitive2d
 
                             basegfx::ColorSteps aColorSteps;
 
-                            // test code here, will be removed later
-                            // if(false)
-                            // {
-                            //     aStart = basegfx::BColor(1.0, 0.0, 0.0); // 
red
-                            //     aEnd = basegfx::BColor(0.0, 0.0, 1.0); // 
blue
-                            //     aColorSteps.emplace_back(0.25, 
basegfx::BColor(0.0, 1.0, 0.0)); // green@25%
-                            // }
+                            // test code here, can/will be removed later
+                            static sal_uInt32 nUseGradientSteps(0);
+                            switch(nUseGradientSteps)
+                            {
+                                case 1:
+                                {
+                                    // just test a nice valid gradient
+                                    aStart = basegfx::BColor(1.0, 0.0, 0.0); 
// red
+                                    aEnd = basegfx::BColor(0.0, 0.0, 1.0); // 
blue
+                                    aColorSteps.emplace_back(0.25, 
basegfx::BColor(0.0, 1.0, 0.0)); // green@25%
+                                    aColorSteps.emplace_back(0.50, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorSteps.emplace_back(0.75, 
basegfx::BColor(1.0, 0.0, 1.0)); // pink@75%
+                                    break;
+                                }
+
+                                case 2:
+                                {
+                                    // single added in-between, no change of 
start/end
+                                    aColorSteps.emplace_back(0.5, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 3:
+                                {
+                                    // check additional StartColor, the one 
given directly has to win
+                                    aColorSteps.emplace_back(0.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 4:
+                                {
+                                    // check additional EndColor, the one 
given directly has to win
+                                    aColorSteps.emplace_back(1.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 5:
+                                {
+                                    // check invalid color (too low index), 
has to be ignored
+                                    aColorSteps.emplace_back(-1.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 6:
+                                {
+                                    // check invalid color (too high index), 
has to be ignored
+                                    aColorSteps.emplace_back(2.0, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 7:
+                                {
+                                    // check in-between single-color part
+                                    aColorSteps.emplace_back(0.3, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorSteps.emplace_back(0.7, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 8:
+                                {
+                                    // check in-between single-color parts
+                                    aColorSteps.emplace_back(0.2, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    aColorSteps.emplace_back(0.4, aEnd);
+                                    aColorSteps.emplace_back(0.6, aStart);
+                                    aColorSteps.emplace_back(0.8, 
basegfx::BColor(1.0, 1.0, 0.0)); // yellow@50%
+                                    break;
+                                }
+
+                                case 9:
+                                {
+                                    // check single-color start area
+                                    aColorSteps.emplace_back(0.6, aStart);
+                                    break;
+                                }
+
+                                case 10:
+                                {
+                                    // check single-color end area
+                                    aColorSteps.emplace_back(0.6, aEnd);
+                                    break;
+                                }
+
+                                default:
+                                {
+                                    break;
+                                }
+                            }
 
                             aGradient = attribute::FillGradientAttribute(
                                 
XGradientStyleToGradientStyle(aXGradient.GetGradientStyle()),

Reply via email to