diff options
author | Armin Le Grand (allotropia) <armin.le.grand.extern@allotropia.de> | 2023-05-24 12:31:48 +0200 |
---|---|---|
committer | Armin Le Grand <Armin.Le.Grand@me.com> | 2023-05-26 10:08:52 +0200 |
commit | c2bea1bedd2ee8bc4007fd23c6cb839a692297a7 (patch) | |
tree | cabb61287db769cae2f412e45125ddba9110249b /basegfx | |
parent | 7d87770986fdfc43dd5d4b514f68026ff6ededcf (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.cxx | 206 | ||||
-rw-r--r-- | basegfx/source/tools/gradienttools.cxx | 97 |
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, |