diff options
-rw-r--r-- | basegfx/source/tools/gradienttools.cxx | 101 | ||||
-rw-r--r-- | drawinglayer/source/attribute/fillgradientattribute.cxx | 40 | ||||
-rw-r--r-- | drawinglayer/source/texture/texture.cxx | 95 | ||||
-rw-r--r-- | include/basegfx/utils/gradienttools.hxx | 23 | ||||
-rw-r--r-- | svx/source/sdr/primitive2d/sdrattributecreator.cxx | 94 |
5 files changed, 292 insertions, 61 deletions
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx index b3bb18f918da..a8d36b9f543d 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 9f6e1f879d42..fa6a0f876cae 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 4c2849366153..2c29c6380ff0 100644 --- a/svx/source/sdr/primitive2d/sdrattributecreator.cxx +++ b/svx/source/sdr/primitive2d/sdrattributecreator.cxx @@ -498,13 +498,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()), |