summaryrefslogtreecommitdiff
path: root/basegfx/source/tools/gradienttools.cxx
diff options
context:
space:
mode:
authorArmin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de>2023-03-13 19:39:34 +0100
committerArmin Le Grand <Armin.Le.Grand@me.com>2023-03-19 17:38:38 +0000
commit01d0019c851b9e942f9a3b94d6dd554fb1adb40c (patch)
tree201cb6d13f57f8fa06128785c1ddb330eeacc349 /basegfx/source/tools/gradienttools.cxx
parentfd299d209d82495eb6528fb444e0b68f41c8ae0d (diff)
MCGR: Model data changes for ColorSteps
Added tooling replaceStart/EndColor to allow simple transition for code that does not immediately adapt to multi color gradients. Also added createColorStepsFromStartEndColor for the same purpose. Adapted XGradient to no longer have Start/EndColor at all, but only use ColorSteps. Adapted all usages of XGradient to no longer use Get/Set/Start/EndColor, but access the ColorSteps instead. Replaced quite some XGradient constructors that used XGradient() as Start/EndColor since this is already the default. Adapted ColorBlending to black AKA Start/EndIntens in XGradient to work now on all ColorSteps in the required linearly-scaled manner. UNO API changes: Added com::sun::star::awt::ColorStep as basic data element that holds a pair of Offset and Color. Added com::sun::star::awt::ColorStepSequence to handle an array of sorted entries. Added com::sun::star::awt::Gradient2 derived from com::sun::star::awt::Gradient, extended by the needed com::sun::star::awt::ColorStepSequence. Added MID_GRADIENT_COLORSTEPSEQUENCE to UNO API to provide access to ColorSteps directly. Adapted XFillGradientItem::QueryValue/PutValue to make use of new UNO API data structures. To do so, added tooling methods for data transition: - fillColorStepSequenceFromColorSteps - fillGradient2FromXGradient - fillColorStepsFromAny - fillXGradientFromAny and adapted - case '0' (all data) - MID_FILLGRADIENT - MID_GRADIENT_COLORSTEPSEQUENCE - MID_GRADIENT_START/ENDCOLOR to make use of these. Tested usage of these in the office. Renamed from GradientStep to GradientStop after discussions with members on the list to make this closer related to other norms/definitions. Also renamed classes and class members to better reflect to GradientStop, so grepping/finding will be easier (e.g. 'Color' just exists pretty often, but 'StopColor' is more precise). Changed the used UNO API class for reprsenting the Color to better reflect to ranges [0.0 .. 1.0] and usage of RGB. Change-Id: I1eeb3e97e81d6785967615d1ff256551fc3b882d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148849 Tested-by: Jenkins Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Diffstat (limited to 'basegfx/source/tools/gradienttools.cxx')
-rw-r--r--basegfx/source/tools/gradienttools.cxx191
1 files changed, 148 insertions, 43 deletions
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx
index 0bb45ba4d523..d43f7899121a 100644
--- a/basegfx/source/tools/gradienttools.cxx
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -21,6 +21,7 @@
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <com/sun/star/awt/Gradient2.hpp>
#include <algorithm>
#include <cmath>
@@ -263,19 +264,123 @@ namespace basegfx
namespace utils
{
+ /* 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;
+
+ const sal_Int32 nLen(aGradient2.ColorStops.getLength());
+
+ if (0 == nLen)
+ return;
+
+ // we have ColorStops
+ rColorStops.clear();
+ rColorStops.reserve(nLen);
+ const css::awt::ColorStop* pSourceColorStop(aGradient2.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 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 ColorSteps vector.
+ 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 ColorSteps with offset < 0.0 (will
+ - contains no ColorStops with offset < 0.0 (will
be removed)
- - contains no ColorSteps with offset > 0.0 (will
+ - contains no ColorStops with offset > 1.0 (will
be removed)
- - contains no ColorSteps with identical offset
- (will be removed, 1st one wins)
+ - contains no two ColorStops with identical offsets
+ (will be removed, 1st one/smallest offset wins
+ which is also given by sort tooling)
- will be sorted from lowest offset to highest
- if all colors are the same, the content will
be reduced to a single entry with offset 0.0
- (StartColor)
+ (force to StartColor)
Some more notes:
- It can happen that the result is empty
@@ -283,30 +388,30 @@ namespace basegfx
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 required on this level
+ no 'StartColor' is technically required
- A entry with 1.0 is not required or forced, so
- no 'EndColor' is required on this level
+ 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 sortAndCorrectColorSteps(ColorSteps& rColorSteps)
+ void sortAndCorrectColorStops(ColorStops& rColorStops)
{
// no content, we are done
- if (rColorSteps.empty())
+ if (rColorStops.empty())
return;
- if (1 == rColorSteps.size())
+ if (1 == rColorStops.size())
{
// no gradient at all, but preserve given color
// and force it to be the StartColor
- rColorSteps[0] = ColorStep(0.0, rColorSteps[0].getColor());
+ rColorStops[0] = ColorStop(0.0, rColorStops[0].getStopColor());
}
// 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(rColorSteps.begin(), rColorSteps.end());
+ std::sort(rColorStops.begin(), rColorStops.end());
// prepare status values
bool bSameColorInit(false);
@@ -318,10 +423,10 @@ namespace basegfx
// 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 < rColorSteps.size(); read++)
+ for (size_t read(0); read < rColorStops.size(); read++)
{
// get offset of entry at read position
- const double rOff(rColorSteps[read].getOffset());
+ const double rOff(rColorStops[read].getStopOffset());
// step over < 0 values
if (basegfx::fTools::less(rOff, 0.0))
@@ -338,23 +443,23 @@ namespace basegfx
if(bSameColorInit)
{
// already initialized, compare
- bAllTheSameColor = bAllTheSameColor && aFirstColor == rColorSteps[read].getColor();
+ bAllTheSameColor = bAllTheSameColor && aFirstColor == rColorStops[read].getStopColor();
}
else
{
// do initialize, remember 1st valid color
bSameColorInit = true;
- aFirstColor = rColorSteps[read].getColor();
+ aFirstColor = rColorStops[read].getStopColor();
}
// copy if write target is empty (write at start) or when
// write target is different to read
- if (0 == write || rOff != rColorSteps[write-1].getOffset())
+ if (0 == write || rOff != rColorStops[write-1].getStopOffset())
{
if (write != read)
{
// copy read to write backwards to close gaps
- rColorSteps[write] = rColorSteps[read];
+ rColorStops[write] = rColorStops[read];
}
// always forward write position
@@ -364,44 +469,44 @@ namespace basegfx
// correct size when length is reduced. write is always at
// last used position + 1
- if (rColorSteps.size() > write)
+ if (rColorStops.size() > write)
{
- rColorSteps.resize(write);
+ rColorStops.resize(write);
}
- if (bSameColorInit && bAllTheSameColor && rColorSteps.size() > 1)
+ if (bSameColorInit && bAllTheSameColor && rColorStops.size() > 1)
{
// id all-the-same color is detected, reset to single
// entry, but also force to StartColor and preserve the color
- rColorSteps.resize(1);
- rColorSteps[0] = ColorStep(0.0, aFirstColor);
+ rColorStops.resize(1);
+ rColorStops[0] = ColorStop(0.0, aFirstColor);
}
}
BColor modifyBColor(
- const ColorSteps& rColorSteps,
+ const ColorStops& rColorStops,
double fScaler,
sal_uInt32 nRequestedSteps)
{
// no color at all, done
- if (rColorSteps.empty())
+ if (rColorStops.empty())
return BColor();
// outside range -> at start
if (fScaler <= 0.0)
- return rColorSteps.front().getColor();
+ return rColorStops.front().getStopColor();
// outside range -> at end
if (fScaler >= 1.0)
- return rColorSteps.back().getColor();
+ 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 == rColorSteps.size())
+ if (2 == rColorStops.size())
{
- const basegfx::BColor aCStart(rColorSteps.front().getColor());
- const basegfx::BColor aCEnd(rColorSteps.back().getColor());
+ const basegfx::BColor aCStart(rColorStops.front().getStopColor());
+ const basegfx::BColor aCEnd(rColorStops.back().getStopColor());
const sal_uInt32 nSteps(
calculateNumberOfSteps(
nRequestedSteps,
@@ -421,25 +526,25 @@ namespace basegfx
// all is good/fast as expected
const auto upperBound(
std::lower_bound(
- rColorSteps.begin(),
- rColorSteps.end(),
- ColorStep(fScaler),
- [](const ColorStep& x, const ColorStep& y) { return x.getOffset() < y.getOffset(); }));
+ rColorStops.begin(),
+ rColorStops.end(),
+ ColorStop(fScaler),
+ [](const ColorStop& x, const ColorStop& y) { return x.getStopOffset() < y.getStopOffset(); }));
// no upper bound, done
- if (rColorSteps.end() == upperBound)
- return rColorSteps.back().getColor();
+ if (rColorStops.end() == upperBound)
+ return rColorStops.back().getStopColor();
// lower bound is one entry back
const auto lowerBound(upperBound - 1);
// no lower bound, done
- if (rColorSteps.end() == lowerBound)
- return rColorSteps.back().getColor();
+ if (rColorStops.end() == lowerBound)
+ return rColorStops.back().getStopColor();
// we have lower and upper bound, get colors
- const BColor aCStart(lowerBound->getColor());
- const BColor aCEnd(upperBound->getColor());
+ const BColor aCStart(lowerBound->getStopColor());
+ const BColor aCEnd(upperBound->getStopColor());
// 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
@@ -456,8 +561,8 @@ namespace basegfx
// get offsets and scale to new [0.0 .. 1.0] relative range for
// partial outer range
- const double fOffsetStart(lowerBound->getOffset());
- const double fOffsetEnd(upperBound->getOffset());
+ const double fOffsetStart(lowerBound->getStopOffset());
+ const double fOffsetEnd(upperBound->getStopOffset());
const double fAdaptedScaler((fScaler - fOffsetStart) / (fOffsetEnd - fOffsetStart));
// interpolate & evtl. apply steps