summaryrefslogtreecommitdiff
path: root/basegfx
diff options
context:
space:
mode:
authorArmin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de>2023-05-12 15:32:51 +0200
committerArmin Le Grand <Armin.Le.Grand@me.com>2023-05-15 15:19:53 +0200
commit438f0752deaf7d6e6d9d1df381b64aca4628e944 (patch)
treef1907b25dad1cbf2604b16c657e11f5623c8b5b2 /basegfx
parentefa965969c6d3dfe5745a535605a6b9a482a03bd (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.mk1
-rw-r--r--basegfx/source/tools/bgradient.cxx770
-rw-r--r--basegfx/source/tools/gradienttools.cxx555
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,