summaryrefslogtreecommitdiff
path: root/basegfx
diff options
context:
space:
mode:
authorArmin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de>2023-05-24 12:31:48 +0200
committerArmin Le Grand <Armin.Le.Grand@me.com>2023-05-26 10:08:52 +0200
commitc2bea1bedd2ee8bc4007fd23c6cb839a692297a7 (patch)
treecabb61287db769cae2f412e45125ddba9110249b /basegfx
parent7d87770986fdfc43dd5d4b514f68026ff6ededcf (diff)
MCGR: Border restoration support
Due to tdf#155362 I added code to be able in case we would need it to convert a BGradient using added tooling from having offsets in the GradientSteps and no border to adapted GradientSteps and border. This is preferrable due to our GradientStyle_RECT (and GradientStyle_ELLIPTICAL too) use that 'non- linear' paint aka move-two-pixels-inside, someone else called it 'frame-paint'. This does not bode well with the border being applied 'linear' at the same time (argh). Thus - while in principle all is correct when re-importing a GradientStyle_RECT with a border after export to pptx, it looks slightly better ('correcter') wen doing so. That is because when being able to and restoring a border at least that border part *is* applied linearly. I took the chance to further apply tooling, move it to classes involved and instead of awt::Gradient2 use more basegfx::BGradient since it can and does host tooling. This is also a preparation to be able to adapt (restore) border in case of turn- around in ODF where the importing instance is before MCGR existance and has to handle Start/EndColor. Needed to take more care with using BGradient instead of awt::Gradient2 for initialization, also better dividing/organization of tooling, already preparation to use for other purposes. Change-Id: I2d3a4240a5ac6fff9211b46642ee80366dc09e3d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152194 Tested-by: Jenkins Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
Diffstat (limited to 'basegfx')
-rw-r--r--basegfx/source/tools/bgradient.cxx206
-rw-r--r--basegfx/source/tools/gradienttools.cxx97
2 files changed, 232 insertions, 71 deletions
diff --git a/basegfx/source/tools/bgradient.cxx b/basegfx/source/tools/bgradient.cxx
index 52aa721f36a6..b7ee0780a2cc 100644
--- a/basegfx/source/tools/bgradient.cxx
+++ b/basegfx/source/tools/bgradient.cxx
@@ -563,6 +563,100 @@ void BColorStops::reverseColorStops()
candidate = BColorStop(1.0 - candidate.getStopOffset(), candidate.getStopColor());
}
+// createSpaceAtStart creates fOffset space at start by
+// translating/scaling all entries to the right
+void BColorStops::createSpaceAtStart(double fOffset)
+{
+ // nothing to do if empty
+ if (empty())
+ return;
+
+ // correct offset to [0.0 .. 1.0]
+ fOffset = std::max(std::min(1.0, fOffset), 0.0);
+
+ // nothing to do if 0.0 == offset
+ if (basegfx::fTools::equalZero(fOffset))
+ return;
+
+ BColorStops aNewStops;
+
+ for (const auto& candidate : *this)
+ {
+ aNewStops.emplace_back(fOffset + (candidate.getStopOffset() * (1.0 - fOffset)),
+ candidate.getStopColor());
+ }
+
+ *this = aNewStops;
+}
+
+// removeSpaceAtStart removes fOffset space from start by
+// translating/scaling entries more or equal to fOffset
+// to the left. Entries less than fOffset will be removed
+void BColorStops::removeSpaceAtStart(double fOffset)
+{
+ // nothing to do if empty
+ if (empty())
+ return;
+
+ // correct factor to [0.0 .. 1.0]
+ fOffset = std::max(std::min(1.0, fOffset), 0.0);
+
+ // nothing to do if fOffset == 0.0
+ if (basegfx::fTools::equalZero(fOffset))
+ return;
+
+ BColorStops aNewStops;
+ const double fMul(basegfx::fTools::equal(fOffset, 1.0) ? 1.0 : 1.0 / (1.0 - fOffset));
+
+ for (const auto& candidate : *this)
+ {
+ if (basegfx::fTools::moreOrEqual(candidate.getStopOffset(), fOffset))
+ {
+ aNewStops.emplace_back((candidate.getStopOffset() - fOffset) * fMul,
+ candidate.getStopColor());
+ }
+ }
+
+ *this = aNewStops;
+}
+
+// try to detect if an empty/no-color-change area exists
+// at the start and return offset to it. Returns 0.0 if not.
+double BColorStops::detectPossibleOffsetAtStart() const
+{
+ BColor aSingleColor;
+ const bool bSingleColor(isSingleColor(aSingleColor));
+
+ // no useful offset for single color
+ if (bSingleColor)
+ return 0.0;
+
+ // here we know that we have at least two colors, so we have a
+ // color change. Find colors left and right of that first color change
+ BColorStops::const_iterator aColorR(begin());
+ BColorStops::const_iterator aColorL(aColorR++);
+
+ // aColorR would 1st get equal to end(), so no need to also check aColorL
+ // for end(). Loop as long as same color. Since we *have* a color change
+ // not even aColorR can get equal to end() before color inequality, but
+ // keep for safety
+ while (aColorR != end() && aColorL->getStopColor() == aColorR->getStopColor())
+ {
+ aColorL++;
+ aColorR++;
+ }
+
+ // also for safety: access values at aColorL below *only*
+ // if not equal to end(), but can theoretically not happen
+ if (aColorL == end())
+ {
+ return 0.0;
+ }
+
+ // return offset (maybe 0.0 what is OK)
+ return aColorL->getStopOffset();
+}
+
std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle)
{
switch (eStyle)
@@ -757,7 +851,24 @@ css::awt::Gradient2 BGradient::getAsGradient2() const
aRetval.StepCount = GetSteps();
// for compatibility, still set StartColor/EndColor
- // const basegfx::BColorStops& rColorStops(GetColorStops());
+ // NOTE: All code after adapting to multi color gradients works
+ // using the ColorSteps, so in principle Start/EndColor might
+ // be either
+ // (a) ignored consequently everywhere or
+ // (b) be set/added consequently everywhere
+ // since this is - in principle - redundant data.
+ // Be aware that e.g. cases like DrawingML::EqualGradients
+ // and others would have to be identified and adapted (!)
+ // Since awt::Gradient2 is UNO API data there might
+ // be cases where just awt::Gradient is transferred, so (b)
+ // is far better backwards compatible and thus more safe, so
+ // all changes will make use of additionally using/setting
+ // these additionally, but will only make use of the given
+ // ColorSteps if these are not empty, assuming that these
+ // already contain Start/EndColor.
+ // In principle that redundancy and that it is conflict-free
+ // could even be checked and asserted, but consequently using
+ // (b) methodically should be safe.
aRetval.StartColor
= static_cast<sal_Int32>(ColorToBColorConverter(aColorStops.front().getStopColor()));
aRetval.EndColor
@@ -765,10 +876,101 @@ css::awt::Gradient2 BGradient::getAsGradient2() const
// fill ColorStops to extended Gradient2
aRetval.ColorStops = aColorStops.getAsColorStopSequence();
- // fillColorStopSequenceFromColorStops(rGradient2.ColorStops, rColorStops);
return aRetval;
}
+
+void BGradient::tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops)
+{
+ // border already set, do not try to recreate
+ if (0 != GetBorder())
+ return;
+
+ BColor aSingleColor;
+ const bool bSingleColor(GetColorStops().isSingleColor(aSingleColor));
+
+ // no need to recreate with single color
+ if (bSingleColor)
+ return;
+
+ const bool bIsAxial(css::awt::GradientStyle_AXIAL == GetGradientStyle());
+
+ if (bIsAxial)
+ {
+ // for axial due to reverse used gradient work reversed
+ aColorStops.reverseColorStops();
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->reverseColorStops();
+ }
+
+ // check if we have space at start of range [0.0 .. 1.0] that
+ // may be interpreted as 'border' -> same color. That may involve
+ // different scenarios, e.g. 1st index > 0.0, but also a non-zero
+ // number of same color entries, or a combination of both
+ const double fOffset(aColorStops.detectPossibleOffsetAtStart());
+
+ if (!basegfx::fTools::equalZero(fOffset))
+ {
+ // we have a border area, indeed re-create
+ aColorStops.removeSpaceAtStart(fOffset);
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->removeSpaceAtStart(fOffset);
+
+ // ...and create border value
+ SetBorder(static_cast<sal_uInt16>(fOffset * 100.0));
+ }
+
+ if (bIsAxial)
+ {
+ // take back reverse
+ aColorStops.reverseColorStops();
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->reverseColorStops();
+ }
+}
+
+void BGradient::tryToApplyBorder()
+{
+ // no border to apply, done
+ if (0 == GetBorder())
+ return;
+
+ // NOTE: no new start node is added. The new ColorStop
+ // mechanism does not need entries at 0.0 and 1.0.
+ // In case this is needed, do that in the caller
+ const double fOffset(GetBorder() * 0.01);
+
+ if (css::awt::GradientStyle_AXIAL == GetGradientStyle())
+ {
+ // for axial due to reverse used gradient work reversed
+ aColorStops.reverseColorStops();
+ aColorStops.createSpaceAtStart(fOffset);
+ aColorStops.reverseColorStops();
+ }
+ else
+ {
+ // apply border to GradientSteps
+ aColorStops.createSpaceAtStart(fOffset);
+ }
+
+ // set changed values
+ SetBorder(0);
+}
+
+void BGradient::tryToApplyStartEndIntensity()
+{
+ // already on default, nothing to apply
+ if (100 == GetStartIntens() && 100 == GetEndIntens())
+ return;
+
+ // apply 'old' blend stuff, blend against black
+ aColorStops.blendToIntensity(GetStartIntens() * 0.01, GetEndIntens() * 0.01,
+ BColor()); // COL_BLACK
+
+ // set values to default
+ SetStartIntens(100);
+ SetEndIntens(100);
+}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx
index d7b1bb167613..366e0b0840b8 100644
--- a/basegfx/source/tools/gradienttools.cxx
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -265,28 +265,15 @@ namespace basegfx
namespace utils
{
- /* Tooling method to extract data from given awt::Gradient2
+ /* Tooling method to extract data from given BGradient
to ColorStops, doing some corrections, partially based
- on given SingleColor.
- This will do quite some preparations for the gradient
- as follows:
- - It will check for single color (resetting rSingleColor when
- this is the case) and return with empty ColorStops
- - It will blend ColorStops to Intensity if StartIntensity/
- EndIntensity != 100 is set in awt::Gradient2, so applying
- that value(s) to the gradient directly
- - It will adapt to Border if Border != 0 is set at the
- given awt::Gradient2, so applying that value to the gradient
- directly
- */
+ on given SingleColor */
void prepareColorStops(
const basegfx::BGradient& rGradient,
BColorStops& rColorStops,
BColor& rSingleColor)
{
- rColorStops = rGradient.GetColorStops();
-
- if (rColorStops.isSingleColor(rSingleColor))
+ if (rGradient.GetColorStops().isSingleColor(rSingleColor))
{
// when single color, preserve value in rSingleColor
// and clear the ColorStops, done.
@@ -294,73 +281,45 @@ namespace basegfx
return;
}
- if (100 != rGradient.GetStartIntens() || 100 != rGradient.GetEndIntens())
+ const bool bAdaptStartEndIntensity(100 != rGradient.GetStartIntens() || 100 != rGradient.GetEndIntens());
+ const bool bAdaptBorder(0 != rGradient.GetBorder());
+
+ if (!bAdaptStartEndIntensity && !bAdaptBorder)
{
- // apply 'old' blend stuff, blend against black
- rColorStops.blendToIntensity(
- rGradient.GetStartIntens() * 0.01,
- rGradient.GetEndIntens() * 0.01,
- basegfx::BColor()); // COL_BLACK
-
- // can lead to single color (e.g. both zero, so all black),
- // so check again
- if (rColorStops.isSingleColor(rSingleColor))
+ // copy unchanged ColorStops & done
+ rColorStops = rGradient.GetColorStops();
+ return;
+ }
+
+ // prepare a copy to work on
+ basegfx::BGradient aWorkCopy(rGradient);
+
+ if (bAdaptStartEndIntensity)
+ {
+ aWorkCopy.tryToApplyStartEndIntensity();
+
+ // this can again lead to single color (e.g. both zero, so
+ // all black), so check again for it
+ if (aWorkCopy.GetColorStops().isSingleColor(rSingleColor))
{
rColorStops.clear();
return;
}
}
- if (0 != rGradient.GetBorder())
+ if (bAdaptBorder)
{
- // apply Border if set
- // NOTE: no new start node is added. The new ColorStop
- // 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.GetBorder() * 0.01);
- BColorStops aNewStops;
-
- for (const auto& candidate : rColorStops)
- {
- if (css::awt::GradientStyle_AXIAL == rGradient.GetGradientStyle())
- {
- // for axial add the 'gap' at the start due to reverse used gradient
- aNewStops.emplace_back((1.0 - fFactor) * candidate.getStopOffset(), candidate.getStopColor());
- }
- else
- {
- // css::awt::GradientStyle_LINEAR
- // case awt::GradientStyle_RADIAL
- // case awt::GradientStyle_ELLIPTICAL
- // case awt::GradientStyle_RECT
- // case awt::GradientStyle_SQUARE
-
- // for all others add the 'gap' at the end
- aNewStops.emplace_back(fFactor + (candidate.getStopOffset() * (1.0 - fFactor)), candidate.getStopColor());
- }
- }
-
- rColorStops = aNewStops;
+ aWorkCopy.tryToApplyBorder();
}
+
+ // extract ColorStops, that's all we need here
+ rColorStops = aWorkCopy.GetColorStops();
}
/* Tooling method to synchronize the given ColorStops.
The intention is that a color GradientStops and an
alpha/transparence GradientStops gets synchronized
- for export.
- For the corrections the single values for color and
- alpha may be used, e.g. when ColorStops is given
- and not empty, but AlphaStops is empty, it will get
- synchronized so that it will have the same number and
- offsets in AlphaStops as in ColorStops, but with
- the given SingleAlpha as value.
- At return it guarantees that both have the same
- number of entries with the same StopOffsets, so
- that synchronized pair of ColorStops can e.g. be used
- to export a Gradient with defined/adapted alpha
- being 'coupled' indirectly using the
- 'FillTransparenceGradient' method (at import time).
- */
+ for export. */
void synchronizeColorStops(
BColorStops& rColorStops,
BColorStops& rAlphaStops,