diff options
author | Armin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de> | 2023-05-12 15:32:51 +0200 |
---|---|---|
committer | Armin Le Grand <Armin.Le.Grand@me.com> | 2023-05-15 15:19:53 +0200 |
commit | 438f0752deaf7d6e6d9d1df381b64aca4628e944 (patch) | |
tree | f1907b25dad1cbf2604b16c657e11f5623c8b5b2 /basegfx | |
parent | efa965969c6d3dfe5745a535605a6b9a482a03bd (diff) |
MCGR: consolidations/cleanups for changes so far
Change-Id: I85cf40e4803b0485bb40349d8e81adc8123666c4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151706
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Diffstat (limited to 'basegfx')
-rw-r--r-- | basegfx/Library_basegfx.mk | 1 | ||||
-rw-r--r-- | basegfx/source/tools/bgradient.cxx | 770 | ||||
-rw-r--r-- | basegfx/source/tools/gradienttools.cxx | 555 |
3 files changed, 801 insertions, 525 deletions
diff --git a/basegfx/Library_basegfx.mk b/basegfx/Library_basegfx.mk index 2f9830d0dcfd..da257e2c9797 100644 --- a/basegfx/Library_basegfx.mk +++ b/basegfx/Library_basegfx.mk @@ -67,6 +67,7 @@ $(eval $(call gb_Library_add_exception_objects,basegfx,\ basegfx/source/range/b3drange \ basegfx/source/raster/rasterconvert3d \ basegfx/source/tools/b2dclipstate \ + basegfx/source/tools/bgradient \ basegfx/source/tools/canvastools \ basegfx/source/tools/gradienttools \ basegfx/source/tools/keystoplerp \ diff --git a/basegfx/source/tools/bgradient.cxx b/basegfx/source/tools/bgradient.cxx new file mode 100644 index 000000000000..7cb1ed85e859 --- /dev/null +++ b/basegfx/source/tools/bgradient.cxx @@ -0,0 +1,770 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <basegfx/utils/bgradient.hxx> +#include <basegfx/utils/gradienttools.hxx> +#include <com/sun/star/awt/Gradient2.hpp> +#include <boost/property_tree/json_parser.hpp> +#include <map> + +typedef std::map<OUString, OUString> StringMap; + +namespace +{ +css::awt::GradientStyle lcl_getStyleFromString(std::u16string_view rStyle) +{ + if (rStyle == u"LINEAR") + return css::awt::GradientStyle_LINEAR; + else if (rStyle == u"AXIAL") + return css::awt::GradientStyle_AXIAL; + else if (rStyle == u"RADIAL") + return css::awt::GradientStyle_RADIAL; + else if (rStyle == u"ELLIPTICAL") + return css::awt::GradientStyle_ELLIPTICAL; + else if (rStyle == u"SQUARE") + return css::awt::GradientStyle_SQUARE; + else if (rStyle == u"RECT") + return css::awt::GradientStyle_RECT; + + return css::awt::GradientStyle_LINEAR; +} + +StringMap lcl_jsonToStringMap(std::u16string_view rJSON) +{ + StringMap aArgs; + if (rJSON.size() && rJSON[0] != '\0') + { + std::stringstream aStream(std::string(OUStringToOString(rJSON, RTL_TEXTENCODING_ASCII_US))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + for (const auto& rPair : aTree) + { + aArgs[OUString::fromUtf8(rPair.first)] + = OUString::fromUtf8(rPair.second.get_value<std::string>(".")); + } + } + return aArgs; +} + +basegfx::BGradient lcl_buildGradientFromStringMap(StringMap& rMap) +{ + basegfx::BGradient aGradient( + basegfx::BColorStops(ColorToBColorConverter(rMap["startcolor"].toInt32(16)).getBColor(), + ColorToBColorConverter(rMap["endcolor"].toInt32(16)).getBColor())); + + aGradient.SetGradientStyle(lcl_getStyleFromString(rMap["style"])); + aGradient.SetAngle(Degree10(rMap["angle"].toInt32())); + + return aGradient; +} +} + +namespace basegfx +{ +void BColorStops::setColorStopSequence(const css::awt::ColorStopSequence& rColorStops) +{ + const sal_Int32 nLen(rColorStops.getLength()); + + if (0 != nLen) + { + // we have ColorStops + reserve(nLen); + const css::awt::ColorStop* pSourceColorStop(rColorStops.getConstArray()); + + for (sal_Int32 a(0); a < nLen; a++, pSourceColorStop++) + { + emplace_back(pSourceColorStop->StopOffset, + BColor(pSourceColorStop->StopColor.Red, pSourceColorStop->StopColor.Green, + pSourceColorStop->StopColor.Blue)); + } + } +} + +BColorStops::BColorStops(const css::awt::ColorStopSequence& rColorStops) +{ + setColorStopSequence(rColorStops); +} + +BColorStops::BColorStops(const css::uno::Any& rVal) +{ + css::awt::Gradient2 aGradient2; + if (rVal >>= aGradient2) + { + setColorStopSequence(aGradient2.ColorStops); + } +} + +// constuctor with two colors to explicitly create a +// BColorStops for a single StartColor @0.0 & EndColor @1.0 +BColorStops::BColorStops(const BColor& rStart, const BColor& rEnd) +{ + emplace_back(0.0, rStart); + emplace_back(1.0, rEnd); +} + +/* Helper to grep the correct ColorStop out of + ColorStops and interpolate as needed for given + relative value in fPosition in the range of [0.0 .. 1.0]. + It also takes care of evtl. given RequestedSteps. + */ +BColor BColorStops::getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps, + BColorStopRange& rLastColorStopRange) const +{ + // no color at all, done + if (empty()) + return BColor(); + + // outside range -> at start + const double fMin(front().getStopOffset()); + if (fPosition < fMin) + return front().getStopColor(); + + // outside range -> at end + const double fMax(back().getStopOffset()); + if (fPosition > fMax) + return back().getStopColor(); + + // special case for the 'classic' case with just two colors: + // we can optimize that and keep the speed/resources low + // by avoiding some calculations and an O(log(N)) array access + if (2 == size()) + { + // if same StopOffset use front color + if (fTools::equal(fMin, fMax)) + return front().getStopColor(); + + const basegfx::BColor aCStart(front().getStopColor()); + const basegfx::BColor aCEnd(back().getStopColor()); + + // if colors are equal just return one + if (aCStart == aCEnd) + return aCStart; + + // calculate Steps + const sal_uInt32 nSteps( + basegfx::utils::calculateNumberOfSteps(nRequestedSteps, 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 may be + // not the classical Start/EndColor (what is allowed) + fPosition = (fPosition - fMin) / (fMax - fMin); + return basegfx::interpolate(aCStart, aCEnd, + nSteps > 1 ? floor(fPosition * nSteps) / double(nSteps - 1) + : fPosition); + } + + // check if we need to newly populate the needed interpolation data + // or if we can re-use from last time. + // If this scope is not entered, we do not need the binary search. It's + // only a single buffered entry, and only used when more than three + // ColorStops exist, but makes a huge difference compared with accessing + // the sorted ColorStop vector each time. + // NOTE: with this simple change I get very high hit rates, e.g. rotating + // a donut with gradient test '1' hit rate is at 0.99909440357755486 + if (rLastColorStopRange.mfOffsetStart == rLastColorStopRange.mfOffsetEnd + || fPosition < rLastColorStopRange.mfOffsetStart + || fPosition > rLastColorStopRange.mfOffsetEnd) + { + // 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::upper_bound(begin(), end(), BColorStop(fPosition), + [](const BColorStop& x, const BColorStop& y) { + return x.getStopOffset() < y.getStopOffset(); + })); + + // no upper bound, done + if (end() == upperBound) + return back().getStopColor(); + + // lower bound is one entry back, access that + const auto lowerBound(upperBound - 1); + + // no lower bound, done + if (end() == lowerBound) + return back().getStopColor(); + + // we have lower and upper bound, get colors and offsets + rLastColorStopRange.maColorStart = lowerBound->getStopColor(); + rLastColorStopRange.maColorEnd = upperBound->getStopColor(); + rLastColorStopRange.mfOffsetStart = lowerBound->getStopOffset(); + rLastColorStopRange.mfOffsetEnd = upperBound->getStopOffset(); + } + + // 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 (rLastColorStopRange.maColorStart == rLastColorStopRange.maColorEnd) + return rLastColorStopRange.maColorStart; + + // calculate number of steps and adapted proportional + // range for scaler in [0.0 .. 1.0] + const double fAdaptedScaler( + (fPosition - rLastColorStopRange.mfOffsetStart) + / (rLastColorStopRange.mfOffsetEnd - rLastColorStopRange.mfOffsetStart)); + const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps( + nRequestedSteps, rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd)); + + // interpolate & evtl. apply steps + return interpolate(rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd, + nSteps > 1 ? floor(fAdaptedScaler * nSteps) / double(nSteps - 1) + : fAdaptedScaler); +} + +/* Tooling method that allows to replace the StartColor in a + vector of ColorStops. A vector in 'ordered state' is expected, + so you may use/have used sortAndCorrect. + This method is for convenience & backwards compatibility, please + think about handling multi-colored gradients directly. + */ +void BColorStops::replaceStartColor(const BColor& rStart) +{ + BColorStops::iterator a1stNonStartColor(begin()); + + // search for highest existing non-StartColor - CAUTION, + // there might be none, one or multiple with StopOffset 0.0 + while (a1stNonStartColor != end() + && basegfx::fTools::lessOrEqual(a1stNonStartColor->getStopOffset(), 0.0)) + a1stNonStartColor++; + + // create new ColorStops by 1st adding new one and then all + // non-StartColor entries + BColorStops aNewColorStops; + + aNewColorStops.reserve(size() + 1); + aNewColorStops.emplace_back(0.0, rStart); + aNewColorStops.insert(aNewColorStops.end(), a1stNonStartColor, end()); + + // assign & done + *this = aNewColorStops; +} + +/* Tooling method that allows to replace the EndColor in a + vector of ColorStops. A vector in 'ordered state' is expected, + so you may use/have used sortAndCorrectColorStops. + This method is for convenience & backwards compatibility, please + think about handling multi-colored gradients directly. + */ +void BColorStops::replaceEndColor(const BColor& rEnd) +{ + // erase all evtl. existing EndColor(s) + while (!empty() && basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0)) + pop_back(); + + // add at the end of existing ColorStops + emplace_back(1.0, rEnd); +} + +/* Tooling method to linearly blend the Colors contained in + a given ColorStop vector against a given Color using the + given intensity values. + The intensity values fStartIntensity, fEndIntensity are + in the range of [0.0 .. 1.0] and describe how much the + blend is supposed to be done at the start color position + and the end color position respectively, where 0.0 means + to fully use the given BlendColor, 1.0 means to not change + the existing color in the ColorStop. + Every color entry in the given ColorStop is blended + relative to it's StopPosition, interpolating the + given intensities with the range [0.0 .. 1.0] to do so. + */ +void BColorStops::blendToIntensity(double fStartIntensity, double fEndIntensity, + const BColor& rBlendColor) +{ + // no entries, done + if (empty()) + return; + + // correct intensities (maybe assert when input was wrong) + fStartIntensity = std::max(std::min(1.0, fStartIntensity), 0.0); + fEndIntensity = std::max(std::min(1.0, fEndIntensity), 0.0); + + // all 100%, no real blend, done + if (basegfx::fTools::equal(fStartIntensity, 1.0) && basegfx::fTools::equal(fEndIntensity, 1.0)) + return; + + // blend relative to StopOffset position + for (auto& candidate : *this) + { + const double fOffset(candidate.getStopOffset()); + const double fIntensity((fStartIntensity * (1.0 - fOffset)) + (fEndIntensity * fOffset)); + candidate = basegfx::BColorStop( + fOffset, basegfx::interpolate(rBlendColor, candidate.getStopColor(), fIntensity)); + } +} + +/* Tooling method to guarantee sort and correctness for + the given ColorStops vector. + A vector fulfilling these conditions is called to be + in 'ordered state'. + + At return, the following conditions are guaranteed: + - contains no ColorStops with offset < 0.0 (will + be removed) + - contains no ColorStops with offset > 1.0 (will + be removed) + - ColorStops with identical offsets are now allowed + - will be sorted from lowest offset to highest + + Some more notes: + - It can happen that the result is empty + - It is allowed to have consecutive entries with + the same color, this represents single-color + regions inside the gradient + - A entry with 0.0 is not required or forced, so + no 'StartColor' is technically required + - A entry with 1.0 is not required or forced, so + no 'EndColor' is technically required + + All this is done in one run (sort + O(N)) without + creating a copy of the data in any form + */ +void BColorStops::sortAndCorrect() +{ + // no content, we are done + if (empty()) + return; + + if (1 == size()) + { + // no gradient at all, but preserve given color + // 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 + const BColorStop aEntry(front()); + clear(); + emplace_back(std::max(0.0, std::min(1.0, aEntry.getStopOffset())), aEntry.getStopColor()); + + // done + return; + } + + // start with sorting the input data. Remember that + // this preserves the order of equal entries, where + // equal is defined here by offset (see use operator==) + std::sort(begin(), end()); + + // prepare status values + size_t write(0); + + // use the paradigm of a band machine with two heads, read + // and write with write <= read all the time. Step over the + // data using read and check for valid entry. If valid, decide + // how to keep it + for (size_t read(0); read < size(); read++) + { + // get offset of entry at read position + double fOff((*this)[read].getStopOffset()); + + if (basegfx::fTools::less(fOff, 0.0) && read + 1 < size()) + { + // value < 0.0 and we have a next entry. check for gradient snippet + // containing 0.0 resp. StartColor + const double fOff2((*this)[read + 1].getStopOffset()); + + if (basegfx::fTools::more(fOff2, 0.0)) + { + // read is the start of a gradient snippet containing 0.0. Correct + // entry to StartColor, interpolate to correct StartColor + (*this)[read] + = BColorStop(0.0, basegfx::interpolate((*this)[read].getStopColor(), + (*this)[read + 1].getStopColor(), + (0.0 - fOff) / (fOff2 - fOff))); + + // adapt fOff + fOff = 0.0; + } + } + + // step over < 0 values, these are outside and will be removed + if (basegfx::fTools::less(fOff, 0.0)) + { + continue; + } + + if (basegfx::fTools::less(fOff, 1.0) && read + 1 < size()) + { + // value < 1.0 and we have a next entry. check for gradient snippet + // containing 1.0 resp. EndColor + const double fOff2((*this)[read + 1].getStopOffset()); + + if (basegfx::fTools::more(fOff2, 1.0)) + { + // read is the start of a gradient snippet containing 1.0. Correct + // next entry to EndColor, interpolate to correct EndColor + (*this)[read + 1] + = BColorStop(1.0, basegfx::interpolate((*this)[read].getStopColor(), + (*this)[read + 1].getStopColor(), + (1.0 - fOff) / (fOff2 - fOff))); + + // adapt fOff + fOff = 1.0; + } + } + + // step over > 1 values; even break, since all following + // entries will also be bigger due to being sorted, so done + if (basegfx::fTools::more(fOff, 1.0)) + { + break; + } + + // entry is valid value at read position + // copy if write target is empty (write at start) or when + // write target is different to read in color or offset + if (0 == write || !((*this)[read] == (*this)[write - 1])) + { + if (write != read) + { + // copy read to write backwards to close gaps + (*this)[write] = (*this)[read]; + } + + // always forward write position + write++; + } + } + + // correct size when length is reduced. write is always at + // last used position + 1 + if (size() > write) + { + if (0 == write) + { + // no valid entries at all, but not empty. This can only happen + // when all entries are below 0.0 or above 1.0 (else a gradient + // snippet spawning over both would have been detected) + if (basegfx::fTools::less(back().getStopOffset(), 0.0)) + { + // all outside too low, rescue last due to being closest to content + const BColor aBackColor(back().getStopColor()); + clear(); + emplace_back(0.0, aBackColor); + } + else // if (basegfx::fTools::more(front().getStopOffset(), 1.0)) + { + // all outside too high, rescue first due to being closest to content + const BColor aFrontColor(front().getStopColor()); + clear(); + emplace_back(1.0, aFrontColor); + } + } + else + { + resize(write); + } + } +} + +bool BColorStops::checkPenultimate() const +{ + // not needed when no ColorStops + if (empty()) + return false; + + // not needed when last ColorStop at the end or outside + if (basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0)) + return false; + + // get penultimate entry + const auto penultimate(rbegin() + 1); + + // if there is none, we need no correction and are done + if (penultimate == rend()) + return false; + + // not needed when the last two ColorStops have different offset, then + // a visible range will be processed already + if (!basegfx::fTools::equal(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 (back().getStopColor() == penultimate->getStopColor()) + return false; + + return true; +} + +/* Tooling method to fill a awt::ColorStopSequence with + the data from the given ColorStops. This is used in + UNO API implementations. + */ +css::awt::ColorStopSequence BColorStops::getAsColorStopSequence() const +{ + css::awt::ColorStopSequence aRetval(size()); + // rColorStopSequence.realloc(rColorStops.size()); + css::awt::ColorStop* pTargetColorStop(aRetval.getArray()); + + for (const auto& candidate : *this) + { + pTargetColorStop->StopOffset = candidate.getStopOffset(); + pTargetColorStop->StopColor = css::rendering::RGBColor(candidate.getStopColor().getRed(), + candidate.getStopColor().getGreen(), + candidate.getStopColor().getBlue()); + pTargetColorStop++; + } + + return aRetval; +} + +/* Tooling method to check if a ColorStop vector is defined + by a single color. It returns true if this is the case. + If true is returned, rSingleColor contains that single + color for convenience. + NOTE: If no ColorStop is defined, a fallback to BColor-default + (which is black) and true will be returned + */ +bool BColorStops::isSingleColor(BColor& rSingleColor) const +{ + if (empty()) + { + rSingleColor = BColor(); + return true; + } + + if (1 == size()) + { + rSingleColor = front().getStopColor(); + return true; + } + + rSingleColor = front().getStopColor(); + + for (auto const& rCandidate : *this) + { + if (rCandidate.getStopColor() != rSingleColor) + return false; + } + + return true; +} + +/* Tooling method to reverse ColorStops, including offsets. + When also mirroring offsets a valid sort keeps valid. + */ +void BColorStops::reverseColorStops() +{ + // can use std::reverse, but also need to adapt offset(s) + std::reverse(begin(), end()); + for (auto& candidate : *this) + candidate = BColorStop(1.0 - candidate.getStopOffset(), candidate.getStopColor()); +} + +std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle) +{ + switch (eStyle) + { + case css::awt::GradientStyle::GradientStyle_LINEAR: + return "LINEAR"; + + case css::awt::GradientStyle::GradientStyle_AXIAL: + return "AXIAL"; + + case css::awt::GradientStyle::GradientStyle_RADIAL: + return "RADIAL"; + + case css::awt::GradientStyle::GradientStyle_ELLIPTICAL: + return "ELLIPTICAL"; + + case css::awt::GradientStyle::GradientStyle_SQUARE: + return "SQUARE"; + + case css::awt::GradientStyle::GradientStyle_RECT: + return "RECT"; + + case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE: + return "MAKE_FIXED_SIZE"; + } + + return ""; +} + +BGradient BGradient::fromJSON(std::u16string_view rJSON) +{ + StringMap aMap(lcl_jsonToStringMap(rJSON)); + return lcl_buildGradientFromStringMap(aMap); +} + +BGradient::BGradient() + : eStyle(css::awt::GradientStyle_LINEAR) + , aColorStops() + , nAngle(0) + , nBorder(0) + , nOfsX(50) + , nOfsY(50) + , nIntensStart(100) + , nIntensEnd(100) + , nStepCount(0) +{ + aColorStops.emplace_back(0.0, BColor(0.0, 0.0, 0.0)); // COL_BLACK + aColorStops.emplace_back(1.0, BColor(1.0, 1.0, 1.0)); // COL_WHITE +} + +BGradient::BGradient(const basegfx::BColorStops& rColorStops, css::awt::GradientStyle eTheStyle, + Degree10 nTheAngle, sal_uInt16 nXOfs, sal_uInt16 nYOfs, sal_uInt16 nTheBorder, + sal_uInt16 nStartIntens, sal_uInt16 nEndIntens, sal_uInt16 nSteps) + : eStyle(eTheStyle) + , aColorStops(rColorStops) + , nAngle(nTheAngle) + , nBorder(nTheBorder) + , nOfsX(nXOfs) + , nOfsY(nYOfs) + , nIntensStart(nStartIntens) + , nIntensEnd(nEndIntens) + , nStepCount(nSteps) +{ + SetColorStops(aColorStops); +} + +BGradient::BGradient(const css::awt::Gradient2& rGradient2) +{ + // set values + SetGradientStyle(rGradient2.Style); + SetAngle(Degree10(rGradient2.Angle)); + SetBorder(rGradient2.Border); + SetXOffset(rGradient2.XOffset); + SetYOffset(rGradient2.YOffset); + SetStartIntens(rGradient2.StartIntensity); + SetEndIntens(rGradient2.EndIntensity); + SetSteps(rGradient2.StepCount); + + // set ColorStops + aColorStops = BColorStops(rGradient2.ColorStops); + aColorStops.sortAndCorrect(); +} + +BGradient::BGradient(const css::uno::Any& rVal) + : BGradient() +{ + if (rVal.has<css::awt::Gradient2>()) + { + // we can use awt::Gradient2 directly + css::awt::Gradient2 aGradient2; + rVal >>= aGradient2; + + // set values + SetGradientStyle(aGradient2.Style); + SetAngle(Degree10(aGradient2.Angle)); + SetBorder(aGradient2.Border); + SetXOffset(aGradient2.XOffset); + SetYOffset(aGradient2.YOffset); + SetStartIntens(aGradient2.StartIntensity); + SetEndIntens(aGradient2.EndIntensity); + SetSteps(aGradient2.StepCount); + + // set ColorStops + aColorStops = BColorStops(aGradient2.ColorStops); + aColorStops.sortAndCorrect(); + } + else if (rVal.has<css::awt::Gradient>()) + { + // use awt::Gradient + css::awt::Gradient aGradient; + rVal >>= aGradient; + + // set values + SetGradientStyle(aGradient.Style); + SetAngle(Degree10(aGradient.Angle)); + SetBorder(aGradient.Border); + SetXOffset(aGradient.XOffset); + SetYOffset(aGradient.YOffset); + SetStartIntens(aGradient.StartIntensity); + SetEndIntens(aGradient.EndIntensity); + SetSteps(aGradient.StepCount); + + // complete data by creating ColorStops from fixe Start/EndColor + aColorStops = BColorStops{ + BColorStop(0.0, ColorToBColorConverter(aGradient.StartColor).getBColor()), + BColorStop(1.0, ColorToBColorConverter(aGradient.EndColor).getBColor()) + }; + } +} + +bool BGradient::operator==(const BGradient& rGradient) const +{ + return (eStyle == rGradient.eStyle && aColorStops == rGradient.aColorStops + && nAngle == rGradient.nAngle && nBorder == rGradient.nBorder + && nOfsX == rGradient.nOfsX && nOfsY == rGradient.nOfsY + && nIntensStart == rGradient.nIntensStart && nIntensEnd == rGradient.nIntensEnd + && nStepCount == rGradient.nStepCount); +} + +void BGradient::SetColorStops(const basegfx::BColorStops& rSteps) +{ + aColorStops = rSteps; + aColorStops.sortAndCorrect(); + if (aColorStops.empty()) + aColorStops.emplace_back(0.0, basegfx::BColor()); +} + +namespace +{ +OUString AsRGBHexString(const ColorToBColorConverter& rVal) +{ + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(6) << sal_uInt32(rVal); + return OUString::createFromAscii(ss.str()); +} +} + +boost::property_tree::ptree BGradient::dumpAsJSON() const +{ + boost::property_tree::ptree aTree; + + aTree.put("style", BGradient::GradientStyleToString(eStyle)); + const ColorToBColorConverter aStart(GetColorStops().front().getStopColor()); + aTree.put("startcolor", AsRGBHexString(aStart.GetRGBColor())); + const ColorToBColorConverter aEnd(GetColorStops().back().getStopColor()); + aTree.put("endcolor", AsRGBHexString(aEnd.GetRGBColor())); + aTree.put("angle", std::to_string(nAngle.get())); + aTree.put("border", std::to_string(nBorder)); + aTree.put("x", std::to_string(nOfsX)); + aTree.put("y", std::to_string(nOfsY)); + aTree.put("intensstart", std::to_string(nIntensStart)); + aTree.put("intensend", std::to_string(nIntensEnd)); + aTree.put("stepcount", std::to_string(nStepCount)); + + return aTree; +} + +css::awt::Gradient2 BGradient::getAsGradient2() const +{ + css::awt::Gradient2 aRetval; + + // standard values + aRetval.Style = GetGradientStyle(); + aRetval.Angle = static_cast<short>(GetAngle()); + aRetval.Border = GetBorder(); + aRetval.XOffset = GetXOffset(); + aRetval.YOffset = GetYOffset(); + aRetval.StartIntensity = GetStartIntens(); + aRetval.EndIntensity = GetEndIntens(); + aRetval.StepCount = GetSteps(); + + // for compatibility, still set StartColor/EndColor + // const basegfx::BColorStops& rColorStops(GetColorStops()); + aRetval.StartColor + = static_cast<sal_Int32>(ColorToBColorConverter(aColorStops.front().getStopColor())); + aRetval.EndColor + = static_cast<sal_Int32>(ColorToBColorConverter(aColorStops.back().getStopColor())); + + // fill ColorStops to extended Gradient2 + aRetval.ColorStops = aColorStops.getAsColorStopSequence(); + // fillColorStopSequenceFromColorStops(rGradient2.ColorStops, rColorStops); + + return aRetval; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx index 372abe08254b..c778a5237676 100644 --- a/basegfx/source/tools/gradienttools.cxx +++ b/basegfx/source/tools/gradienttools.cxx @@ -265,38 +265,6 @@ namespace basegfx namespace utils { - /* Internal helper to convert ::Color from tools::color.hxx to BColor - without the need to link against tools library. Be on the - safe side by using the same union - */ - namespace { - struct ColorToBColorConverter - { - union { - sal_uInt32 mValue; - struct { -#ifdef OSL_BIGENDIAN - sal_uInt8 T; - sal_uInt8 R; - sal_uInt8 G; - sal_uInt8 B; -#else - sal_uInt8 B; - sal_uInt8 G; - sal_uInt8 R; - sal_uInt8 T; -#endif - }; - }; - - ColorToBColorConverter(sal_uInt32 nColor) : mValue(nColor) { T=0; } - BColor getBColor() const - { - return BColor(R / 255.0, G / 255.0, B / 255.0); - } - }; - } - /// Tooling method to fill awt::Gradient2 from data contained in the given Any bool fillGradient2FromAny(css::awt::Gradient2& rGradient, const css::uno::Any& rVal) { @@ -327,11 +295,10 @@ namespace basegfx rGradient.StepCount = aTmp.StepCount; // complete data by creating ColorStops for awt::Gradient2 - fillColorStopSequenceFromColorStops( - rGradient.ColorStops, - ColorStops { - ColorStop(0.0, ColorToBColorConverter(aTmp.StartColor).getBColor()), - ColorStop(1.0, ColorToBColorConverter(aTmp.EndColor).getBColor()) }); + const BColorStops aTempColorStops { + BColorStop(0.0, ColorToBColorConverter(aTmp.StartColor).getBColor()), + BColorStop(1.0, ColorToBColorConverter(aTmp.EndColor).getBColor()) }; + rGradient.ColorStops = aTempColorStops.getAsColorStopSequence(); bRetval = true; } } @@ -355,12 +322,12 @@ namespace basegfx */ void prepareColorStops( const com::sun::star::awt::Gradient2& rGradient, - ColorStops& rColorStops, + BColorStops& rColorStops, BColor& rSingleColor) { - fillColorStopsFromGradient2(rColorStops, rGradient); + rColorStops = BColorStops(rGradient.ColorStops); - if (isSingleColor(rColorStops, rSingleColor)) + if (rColorStops.isSingleColor(rSingleColor)) { // when single color, preserve value in rSingleColor // and clear the ColorStops, done. @@ -371,15 +338,14 @@ namespace basegfx if (rGradient.StartIntensity != 100 || rGradient.EndIntensity != 100) { // apply 'old' blend stuff, blend against black - blendColorStopsToIntensity( - rColorStops, + rColorStops.blendToIntensity( rGradient.StartIntensity * 0.01, rGradient.EndIntensity * 0.01, basegfx::BColor()); // COL_BLACK // can lead to single color (e.g. both zero, so all black), // so check again - if (isSingleColor(rColorStops, rSingleColor)) + if (rColorStops.isSingleColor(rSingleColor)) { rColorStops.clear(); return; @@ -393,7 +359,7 @@ namespace basegfx // mechanism does not need entries at 0.0 and 1.0. // In case this is needed, do that in the caller const double fFactor(rGradient.Border * 0.01); - ColorStops aNewStops; + BColorStops aNewStops; for (const auto& candidate : rColorStops) { @@ -437,8 +403,8 @@ namespace basegfx 'FillTransparenceGradient' method (at import time). */ void synchronizeColorStops( - ColorStops& rColorStops, - ColorStops& rAlphaStops, + BColorStops& rColorStops, + BColorStops& rAlphaStops, const BColor& rSingleColor, const BColor& rSingleAlpha) { @@ -448,12 +414,12 @@ namespace basegfx { // no AlphaStops and no ColorStops // create two-stop fallbacks for both - rColorStops = ColorStops { - ColorStop(0.0, rSingleColor), - ColorStop(1.0, rSingleColor) }; - rAlphaStops = ColorStops { - ColorStop(0.0, rSingleAlpha), - ColorStop(1.0, rSingleAlpha) }; + rColorStops = BColorStops { + BColorStop(0.0, rSingleColor), + BColorStop(1.0, rSingleColor) }; + rAlphaStops = BColorStops { + BColorStop(0.0, rSingleAlpha), + BColorStop(1.0, rSingleAlpha) }; } else { @@ -489,8 +455,8 @@ namespace basegfx if (!bNeedToSyncronize) { // check for same StopOffsets - ColorStops::const_iterator aCurrColor(rColorStops.begin()); - ColorStops::const_iterator aCurrAlpha(rAlphaStops.begin()); + BColorStops::const_iterator aCurrColor(rColorStops.begin()); + BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin()); while (!bNeedToSyncronize && aCurrColor != rColorStops.end() && @@ -511,12 +477,12 @@ namespace basegfx if (bNeedToSyncronize) { // synchronize sizes & StopOffsets - ColorStops::const_iterator aCurrColor(rColorStops.begin()); - ColorStops::const_iterator aCurrAlpha(rAlphaStops.begin()); - ColorStops aNewColor; - ColorStops aNewAlpha; - ColorStopRange aColorStopRange; - ColorStopRange aAlphaStopRange; + BColorStops::const_iterator aCurrColor(rColorStops.begin()); + BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin()); + BColorStops aNewColor; + BColorStops aNewAlpha; + BColorStops::BColorStopRange aColorStopRange; + BColorStops::BColorStopRange aAlphaStopRange; bool bRealChange(false); do { @@ -532,14 +498,14 @@ namespace basegfx { // copy color, create alpha aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor()); - aNewAlpha.emplace_back(fColorOff, utils::modifyBColor(rAlphaStops, fColorOff, 0, aAlphaStopRange)); + aNewAlpha.emplace_back(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange)); bRealChange = true; aCurrColor++; } else if (fTools::more(fColorOff, fAlphaOff)) { // copy alpha, create color - aNewColor.emplace_back(fAlphaOff, utils::modifyBColor(rColorStops, fAlphaOff, 0, aColorStopRange)); + aNewColor.emplace_back(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange)); aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor()); bRealChange = true; aCurrAlpha++; @@ -556,14 +522,14 @@ namespace basegfx else if (bColor) { const double fColorOff(aCurrColor->getStopOffset()); - aNewAlpha.emplace_back(fColorOff, utils::modifyBColor(rAlphaStops, fColorOff, 0, aAlphaStopRange)); + aNewAlpha.emplace_back(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange)); bRealChange = true; aCurrColor++; } else if (bAlpha) { const double fAlphaOff(aCurrAlpha->getStopOffset()); - aNewColor.emplace_back(fAlphaOff, utils::modifyBColor(rColorStops, fAlphaOff, 0, aColorStopRange)); + aNewColor.emplace_back(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange)); bRealChange = true; aCurrAlpha++; } @@ -586,467 +552,6 @@ namespace basegfx } } - /* Tooling method to linearly blend the Colors contained in - a given ColorStop vector against a given Color using the - given intensity values. - The intensity values fStartIntensity, fEndIntensity are - in the range of [0.0 .. 1.0] and describe how much the - blend is supposed to be done at the start color position - and the end color position respectively, where 0.0 means - to fully use the given BlendColor, 1.0 means to not change - the existing color in the ColorStop. - Every color entry in the given ColorStop is blended - relative to it's StopPosition, interpolating the - given intensities with the range [0.0 .. 1.0] to do so. - */ - void blendColorStopsToIntensity(ColorStops& rColorStops, double fStartIntensity, double fEndIntensity, const basegfx::BColor& rBlendColor) - { - // no entries, done - if (rColorStops.empty()) - return; - - // correct intensities (maybe assert when input was wrong) - fStartIntensity = std::max(std::min(1.0, fStartIntensity), 0.0); - fEndIntensity = std::max(std::min(1.0, fEndIntensity), 0.0); - - // all 100%, no real blend, done - if (basegfx::fTools::equal(fStartIntensity, 1.0) && basegfx::fTools::equal(fEndIntensity, 1.0)) - return; - - // blend relative to StopOffset position - for (auto& candidate : rColorStops) - { - const double fOffset(candidate.getStopOffset()); - const double fIntensity((fStartIntensity * (1.0 - fOffset)) + (fEndIntensity * fOffset)); - candidate = basegfx::ColorStop( - fOffset, - basegfx::interpolate(rBlendColor, candidate.getStopColor(), fIntensity)); - } - } - - /* Tooling method to check if a ColorStop vector is defined - by a single color. It returns true if this is the case. - If true is returned, rSingleColor contains that single - color for convenience. - NOTE: If no ColorStop is defined, a fallback to BColor-default - (which is black) and true will be returned - */ - bool isSingleColor(const ColorStops& rColorStops, BColor& rSingleColor) - { - if (rColorStops.empty()) - { - rSingleColor = BColor(); - return true; - } - - if (1 == rColorStops.size()) - { - rSingleColor = rColorStops.front().getStopColor(); - return true; - } - - rSingleColor = rColorStops.front().getStopColor(); - - for (auto const& rCandidate : rColorStops) - { - if (rCandidate.getStopColor() != rSingleColor) - return false; - } - - return true; - } - - /* 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 - awt::Gradient2. - */ - void fillColorStopsFromGradient2(ColorStops& rColorStops, const com::sun::star::awt::Gradient2& rGradient) - { - const sal_Int32 nLen(rGradient.ColorStops.getLength()); - - if (0 == nLen) - return; - - // we have ColorStops - rColorStops.clear(); - rColorStops.reserve(nLen); - const css::awt::ColorStop* pSourceColorStop(rGradient.ColorStops.getConstArray()); - - for (sal_Int32 a(0); a < nLen; a++, pSourceColorStop++) - { - rColorStops.emplace_back( - pSourceColorStop->StopOffset, - BColor(pSourceColorStop->StopColor.Red, pSourceColorStop->StopColor.Green, pSourceColorStop->StopColor.Blue)); - } - } - - /* 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 - extracted, converted and copied into the given ColorStops. - */ - void fillColorStopsFromAny(ColorStops& rColorStops, const css::uno::Any& rVal) - { - css::awt::Gradient2 aGradient2; - if (!(rVal >>= aGradient2)) - return; - - fillColorStopsFromGradient2(rColorStops, aGradient2); - } - - /* Tooling method to fill a awt::ColorStopSequence with - the data from the given ColorStops. This is used in - UNO API implementations. - */ - void fillColorStopSequenceFromColorStops(css::awt::ColorStopSequence& rColorStopSequence, const ColorStops& rColorStops) - { - // fill ColorStops to extended Gradient2 - rColorStopSequence.realloc(rColorStops.size()); - css::awt::ColorStop* pTargetColorStop(rColorStopSequence.getArray()); - - for (const auto& candidate : rColorStops) - { - pTargetColorStop->StopOffset = candidate.getStopOffset(); - pTargetColorStop->StopColor = css::rendering::RGBColor( - candidate.getStopColor().getRed(), - candidate.getStopColor().getGreen(), - candidate.getStopColor().getBlue()); - pTargetColorStop++; - } - } - - /* Tooling method that allows to replace the StartColor in a - vector of ColorStops. A vector in 'ordered state' is expected, - so you may use/have used sortAndCorrectColorStops, see below. - This method is for convenience & backwards compatibility, please - think about handling multi-colored gradients directly. - */ - void replaceStartColor(ColorStops& rColorStops, const BColor& rStart) - { - ColorStops::iterator a1stNonStartColor(rColorStops.begin()); - - // search for highest existing non-StartColor - while (a1stNonStartColor != rColorStops.end() && basegfx::fTools::lessOrEqual(a1stNonStartColor->getStopOffset(), 0.0)) - a1stNonStartColor++; - - // create new ColorStops by 1st adding new one and then all - // non-StartColor entries - ColorStops aNewColorStops; - - aNewColorStops.reserve(rColorStops.size() + 1); - aNewColorStops.emplace_back(0.0, rStart); - aNewColorStops.insert(aNewColorStops.end(), a1stNonStartColor, rColorStops.end()); - - // assign & done - rColorStops = aNewColorStops; - } - - /* Tooling method that allows to replace the EndColor in a - vector of ColorStops. A vector in 'ordered state' is expected, - so you may use/have used sortAndCorrectColorStops, see below. - This method is for convenience & backwards compatibility, please - think about handling multi-colored gradients directly. - */ - void replaceEndColor(ColorStops& rColorStops, const BColor& rEnd) - { - // erase all evtl. existing EndColor(s) - while (!rColorStops.empty() && basegfx::fTools::moreOrEqual(rColorStops.back().getStopOffset(), 1.0)) - rColorStops.pop_back(); - - // add at the end of existing ColorStops - rColorStops.emplace_back(1.0, rEnd); - } - - // Tooling method to quickly create a ColorStop vector for a given set of Start/EndColor - ColorStops createColorStopsFromStartEndColor(const BColor& rStart, const BColor& rEnd) - { - return ColorStops { - ColorStop(0.0, rStart), - ColorStop(1.0, rEnd) }; - } - - /* Tooling method to guarantee sort and correctness for - the given ColorStops vector. - A vector fulfilling these conditions is called to be - in 'ordered state'. - - At return, the following conditions are guaranteed: - - contains no ColorStops with offset < 0.0 (will - be removed) - - contains no ColorStops with offset > 1.0 (will - be removed) - - ColorStops with identical offsets are now allowed - - will be sorted from lowest offset to highest - - Some more notes: - - It can happen that the result is empty - - It is allowed to have consecutive entries with - the same color, this represents single-color - regions inside the gradient - - A entry with 0.0 is not required or forced, so - no 'StartColor' is technically required - - A entry with 1.0 is not required or forced, so - no 'EndColor' is technically required - - All this is done in one run (sort + O(N)) without - creating a copy of the data in any form - */ - void sortAndCorrectColorStops(ColorStops& rColorStops) - { - // no content, we are done - if (rColorStops.empty()) - return; - - if (1 == rColorStops.size()) - { - // no gradient at all, but preserve given color - // 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 - // this preserves the order of equal entries, where - // equal is defined here by offset (see use operator==) - std::sort(rColorStops.begin(), rColorStops.end()); - - // prepare status values - size_t write(0); - - // use the paradigm of a band machine with two heads, read - // and write with write <= read all the time. Step over the - // data using read and check for valid entry. If valid, decide - // how to keep it - for (size_t read(0); read < rColorStops.size(); read++) - { - // get offset of entry at read position - double fOff(rColorStops[read].getStopOffset()); - - if (basegfx::fTools::less(fOff, 0.0) && read + 1 < rColorStops.size()) - { - // value < 0.0 and we have a next entry. check for gradient snippet - // containing 0.0 resp. StartColor - const double fOff2(rColorStops[read + 1].getStopOffset()); - - if (basegfx::fTools::more(fOff2, 0.0)) - { - // read is the start of a gradient snippet containing 0.0. Correct - // entry to StartColor, interpolate to correct StartColor - rColorStops[read] = ColorStop(0.0, basegfx::interpolate( - rColorStops[read].getStopColor(), - rColorStops[read + 1].getStopColor(), - (0.0 - fOff) / (fOff2 - fOff))); - - // adapt fOff - fOff = 0.0; - } - } - - // step over < 0 values, these are outside and will be removed - if (basegfx::fTools::less(fOff, 0.0)) - { - continue; - } - - if (basegfx::fTools::less(fOff, 1.0) && read + 1 < rColorStops.size()) - { - // value < 1.0 and we have a next entry. check for gradient snippet - // containing 1.0 resp. EndColor - const double fOff2(rColorStops[read + 1].getStopOffset()); - - if (basegfx::fTools::more(fOff2, 1.0)) - { - // read is the start of a gradient snippet containing 1.0. Correct - // next entry to EndColor, interpolate to correct EndColor - rColorStops[read + 1] = ColorStop(1.0, basegfx::interpolate( - rColorStops[read].getStopColor(), - rColorStops[read + 1].getStopColor(), - (1.0 - fOff) / (fOff2 - fOff))); - - // adapt fOff - fOff = 1.0; - } - } - - // step over > 1 values; even break, since all following - // entries will also be bigger due to being sorted, so done - if (basegfx::fTools::more(fOff, 1.0)) - { - break; - } - - // entry is valid value at read position - // copy if write target is empty (write at start) or when - // write target is different to read in color or offset - if (0 == write || !(rColorStops[read] == rColorStops[write-1])) - { - if (write != read) - { - // copy read to write backwards to close gaps - rColorStops[write] = rColorStops[read]; - } - - // always forward write position - write++; - } - } - - // correct size when length is reduced. write is always at - // last used position + 1 - if (rColorStops.size() > write) - { - if (0 == write) - { - // no valid entries at all, but not empty. This can only happen - // when all entries are below 0.0 or above 1.0 (else a gradient - // snippet spawning over both would have been detected) - if (basegfx::fTools::less(rColorStops.back().getStopOffset(), 0.0)) - { - // all outside too low, rescue last due to being closest to content - rColorStops = ColorStops { ColorStop(0.0, rColorStops.back().getStopColor()) }; - } - else // if (basegfx::fTools::more(rColorStops.front().getStopOffset(), 1.0)) - { - // all outside too high, rescue first due to being closest to content - rColorStops = ColorStops { ColorStop(1.0, rColorStops.front().getStopColor()) }; - } - } - else - { - rColorStops.resize(write); - } - } - } - - BColor modifyBColor( - const ColorStops& rColorStops, - double fScaler, - sal_uInt32 nRequestedSteps, - ColorStopRange& rLastColorStopRange) - { - // no color at all, done - if (rColorStops.empty()) - return BColor(); - - // outside range -> at start - const double fMin(rColorStops.front().getStopOffset()); - if (fScaler < fMin) - return rColorStops.front().getStopColor(); - - // outside range -> at end - const double fMax(rColorStops.back().getStopOffset()); - if (fScaler > fMax) - return rColorStops.back().getStopColor(); - - // special case for the 'classic' case with just two colors: - // we can optimize that and keep the speed/resources low - // 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( - calculateNumberOfSteps( - nRequestedSteps, - 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 may be - // not the classical Start/EndColor (what is allowed) - fScaler = (fScaler - fMin) / (fMax - fMin); - return basegfx::interpolate( - aCStart, - aCEnd, - nSteps > 1 ? floor(fScaler * nSteps) / double(nSteps - 1) : fScaler); - } - - // check if we need to newly populate the needed interpolation data - // or if we can re-use from last time. - // If this scope is not entered, we do not need the binary search. It's - // only a single buffered entry, and only used when more than three - // ColorStops exist, but makes a huge difference compared with accessing - // the sorted ColorStop vector each time. - // NOTE: with this simple change I get very high hit rates, e.g. rotating - // a donut with gradient test '1' hit rate is at 0.99909440357755486 - if (rLastColorStopRange.mfOffsetStart == rLastColorStopRange.mfOffsetEnd - || fScaler < rLastColorStopRange.mfOffsetStart - || fScaler > rLastColorStopRange.mfOffsetEnd) - { - // 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::upper_bound( - rColorStops.begin(), - rColorStops.end(), - ColorStop(fScaler), - [](const ColorStop& x, const ColorStop& y) { return x.getStopOffset() < y.getStopOffset(); })); - - // no upper bound, done - if (rColorStops.end() == upperBound) - return rColorStops.back().getStopColor(); - - // lower bound is one entry back, access that - const auto lowerBound(upperBound - 1); - - // no lower bound, done - if (rColorStops.end() == lowerBound) - return rColorStops.back().getStopColor(); - - // we have lower and upper bound, get colors and offsets - rLastColorStopRange.maColorStart = lowerBound->getStopColor(); - rLastColorStopRange.maColorEnd = upperBound->getStopColor(); - rLastColorStopRange.mfOffsetStart = lowerBound->getStopOffset(); - rLastColorStopRange.mfOffsetEnd = upperBound->getStopOffset(); - } - - // 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 (rLastColorStopRange.maColorStart == rLastColorStopRange.maColorEnd) - return rLastColorStopRange.maColorStart; - - // calculate number of steps and adapted proportional - // range for scaler in [0.0 .. 1.0] - const double fAdaptedScaler((fScaler - rLastColorStopRange.mfOffsetStart) / - (rLastColorStopRange.mfOffsetEnd - rLastColorStopRange.mfOffsetStart)); - const sal_uInt32 nSteps( - calculateNumberOfSteps( - nRequestedSteps, - rLastColorStopRange.maColorStart, - rLastColorStopRange.maColorEnd)); - - // interpolate & evtl. apply steps - return interpolate( - rLastColorStopRange.maColorStart, - rLastColorStopRange.maColorEnd, - nSteps > 1 ? floor(fAdaptedScaler * nSteps) / double(nSteps - 1) : fAdaptedScaler); - } - sal_uInt32 calculateNumberOfSteps( sal_uInt32 nRequestedSteps, const BColor& rStart, |