basctl/inc/pch/precompiled_basctl.hxx | 1 basegfx/Library_basegfx.mk | 1 basegfx/source/tools/bgradient.cxx | 770 ++++++++++++++ basegfx/source/tools/gradienttools.cxx | 555 ---------- chart2/inc/pch/precompiled_chartcontroller.hxx | 1 chart2/qa/extras/chart2import.cxx | 8 chart2/source/controller/main/ChartController_Tools.cxx | 11 cui/source/inc/cuitabarea.hxx | 8 cui/source/tabpages/tpgradnt.cxx | 41 cui/source/tabpages/tptrans.cxx | 18 drawinglayer/inc/texture/texture.hxx | 21 drawinglayer/qa/unit/vclpixelprocessor2d.cxx | 61 - drawinglayer/source/attribute/fillgradientattribute.cxx | 26 drawinglayer/source/primitive3d/textureprimitive3d.cxx | 6 drawinglayer/source/processor3d/defaultprocessor3d.cxx | 6 drawinglayer/source/texture/texture.cxx | 146 +- drawinglayer/source/tools/wmfemfhelper.cxx | 20 filter/source/msfilter/msdffimp.cxx | 8 filter/source/msfilter/svdfppt.cxx | 22 include/basegfx/utils/bgradient.hxx | 323 +++++ include/basegfx/utils/gradienttools.hxx | 242 ---- include/drawinglayer/attribute/fillgradientattribute.hxx | 15 include/svx/sidebar/AreaPropertyPanelBase.hxx | 22 include/svx/sidebar/AreaTransparencyGradientPopup.hxx | 2 include/svx/xflftrit.hxx | 4 include/svx/xflgrit.hxx | 13 include/svx/xgrad.hxx | 85 - include/svx/xtable.hxx | 8 oox/qa/unit/drawingml.cxx | 3 oox/qa/unit/shape.cxx | 22 oox/source/drawingml/fillproperties.cxx | 18 oox/source/export/chartexport.cxx | 5 oox/source/export/drawingml.cxx | 29 reportdesign/Library_rptui.mk | 1 reportdesign/inc/pch/precompiled_rptui.hxx | 1 reportdesign/source/ui/misc/UITools.cxx | 4 reportdesign/source/ui/report/ReportController.cxx | 4 sc/inc/pch/precompiled_sc.hxx | 1 sc/qa/unit/subsequent_filters_test3.cxx | 4 sc/source/ui/drawfunc/drawsh.cxx | 2 sd/inc/pch/precompiled_sdui.hxx | 1 sd/qa/unit/export-tests-ooxml1.cxx | 6 sd/qa/unit/export-tests-ooxml2.cxx | 3 sd/qa/unit/export-tests-ooxml3.cxx | 6 sd/qa/unit/misc-tests.cxx | 3 sd/qa/unit/uiimpress.cxx | 4 sd/source/core/drawdoc4.cxx | 18 sd/source/ui/sidebar/SlideBackground.cxx | 21 sd/source/ui/sidebar/SlideBackground.hxx | 7 sd/source/ui/view/drviews2.cxx | 2 sd/source/ui/view/drviews7.cxx | 2 sd/source/ui/view/drviews9.cxx | 21 solenv/clang-format/excludelist | 1 svx/inc/pch/precompiled_svx.hxx | 1 svx/source/customshapes/EnhancedCustomShape2d.cxx | 11 svx/source/sdr/primitive2d/sdrattributecreator.cxx | 317 ----- svx/source/sidebar/area/AreaPropertyPanelBase.cxx | 24 svx/source/sidebar/area/AreaTransparencyGradientPopup.cxx | 13 svx/source/svdraw/gradtrns.cxx | 8 svx/source/svdraw/gradtrns.hxx | 5 svx/source/svdraw/svdetc.cxx | 3 svx/source/svdraw/svdfmtf.cxx | 31 svx/source/svdraw/svdmrkv.cxx | 3 svx/source/svdraw/svdoashp.cxx | 4 svx/source/tbxctrls/fillctrl.cxx | 4 svx/source/unodraw/XPropertyTable.cxx | 55 - svx/source/unodraw/unobrushitemhelper.cxx | 8 svx/source/xoutdev/xattr.cxx | 301 ----- svx/source/xoutdev/xpool.cxx | 8 svx/source/xoutdev/xtabgrdt.cxx | 21 svx/source/xoutdev/xtable.cxx | 2 sw/inc/pch/precompiled_msword.hxx | 1 sw/inc/pch/precompiled_sw.hxx | 1 sw/inc/pch/precompiled_swui.hxx | 1 sw/qa/extras/odfexport/odfexport.cxx | 5 sw/qa/extras/ooxmlexport/ooxmlexport2.cxx | 5 sw/qa/extras/rtfexport/rtfexport.cxx | 7 sw/source/core/unocore/unoframe.cxx | 8 sw/source/filter/ww8/docxattributeoutput.cxx | 2 sw/source/filter/ww8/rtfattributeoutput.cxx | 4 sw/source/uibase/docvw/HeaderFooterWin.cxx | 2 sw/source/uibase/docvw/ShadowOverlayObject.cxx | 6 sw/source/uibase/shells/drawdlg.cxx | 4 sw/source/uibase/sidebar/PageStylesPanel.cxx | 14 sw/source/uibase/sidebar/PageStylesPanel.hxx | 2 sw/source/uibase/uiview/viewtab.cxx | 4 86 files changed, 1713 insertions(+), 1805 deletions(-)
New commits: commit 438f0752deaf7d6e6d9d1df381b64aca4628e944 Author: Armin Le Grand (allotropia) <armin.le.grand.ext...@allotropia.de> AuthorDate: Fri May 12 15:32:51 2023 +0200 Commit: Armin Le Grand <armin.le.gr...@me.com> CommitDate: Mon May 15 15:19:53 2023 +0200 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.gr...@me.com> diff --git a/basctl/inc/pch/precompiled_basctl.hxx b/basctl/inc/pch/precompiled_basctl.hxx index 4f3566f3f6ea..257159149a3b 100644 --- a/basctl/inc/pch/precompiled_basctl.hxx +++ b/basctl/inc/pch/precompiled_basctl.hxx @@ -499,7 +499,6 @@ #include <svx/svxdllapi.h> #include <svx/xdash.hxx> #include <svx/xdef.hxx> -#include <svx/xgrad.hxx> #include <svx/xhatch.hxx> #include <svx/xpoly.hxx> #include <svx/xtable.hxx> 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, diff --git a/chart2/inc/pch/precompiled_chartcontroller.hxx b/chart2/inc/pch/precompiled_chartcontroller.hxx index 6839b807696d..ba4ccbddf15e 100644 --- a/chart2/inc/pch/precompiled_chartcontroller.hxx +++ b/chart2/inc/pch/precompiled_chartcontroller.hxx @@ -409,7 +409,6 @@ #include <svx/svxdllapi.h> #include <svx/xdash.hxx> #include <svx/xdef.hxx> -#include <svx/xgrad.hxx> #include <svx/xhatch.hxx> #include <svx/xit.hxx> #include <svx/xpoly.hxx> diff --git a/chart2/qa/extras/chart2import.cxx b/chart2/qa/extras/chart2import.cxx index fc5caf42c3a7..890841a79849 100644 --- a/chart2/qa/extras/chart2import.cxx +++ b/chart2/qa/extras/chart2import.cxx @@ -628,9 +628,7 @@ CPPUNIT_TEST_FIXTURE(Chart2ImportTest, testBnc889755) uno::Reference<beans::XPropertySet> xShapeProps(xPage->getByIndex(4), uno::UNO_QUERY_THROW); awt::Gradient2 aTransparence; xShapeProps->getPropertyValue("FillTransparenceGradient") >>= aTransparence; - - basegfx::ColorStops aColorStops; - basegfx::utils::fillColorStopsFromGradient2(aColorStops, aTransparence); + const basegfx::BColorStops aColorStops(aTransparence.ColorStops); CPPUNIT_ASSERT_EQUAL(size_t(3), aColorStops.size()); CPPUNIT_ASSERT(basegfx::fTools::equal(aColorStops[0].getStopOffset(), 0.0)); @@ -673,9 +671,7 @@ CPPUNIT_TEST_FIXTURE(Chart2ImportTest, testTransparencyGradientValue) uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY); uno::Any rTransparenceValue = xTransparenceGradient->getByName(sTranspGradientName); CPPUNIT_ASSERT(rTransparenceValue >>= aTransparenceGradient); - - basegfx::ColorStops aColorStops; - basegfx::utils::fillColorStopsFromGradient2(aColorStops, aTransparenceGradient); + const basegfx::BColorStops aColorStops(aTransparenceGradient.ColorStops); // MCGR: Use the whole completely imported transparency gradient to check for correctness CPPUNIT_ASSERT_EQUAL(size_t(2), aColorStops.size()); diff --git a/chart2/source/controller/main/ChartController_Tools.cxx b/chart2/source/controller/main/ChartController_Tools.cxx index acc5dd950391..1a8f5071982f 100644 --- a/chart2/source/controller/main/ChartController_Tools.cxx +++ b/chart2/source/controller/main/ChartController_Tools.cxx @@ -74,7 +74,6 @@ #include <svx/unoapi.hxx> #include <svx/unopage.hxx> #include <svx/unoshape.hxx> -#include <svx/xgrad.hxx> #include <PropertyHelper.hxx> #include <LibreOfficeKit/LibreOfficeKitEnums.h> @@ -957,8 +956,8 @@ void ChartController::executeDispatch_FillColor(sal_uInt32 nColor) void ChartController::executeDispatch_FillGradient(std::u16string_view sJSONGradient) { - XGradient aXGradient = XGradient::fromJSON(sJSONGradient); - css::awt::Gradient aGradient = aXGradient.toGradientUNO(); + basegfx::BGradient aBGradient = basegfx::BGradient::fromJSON(sJSONGradient); + css::awt::Gradient aGradient = aBGradient.getAsGradient2(); try { @@ -973,9 +972,9 @@ void ChartController::executeDispatch_FillGradient(std::u16string_view sJSONGrad if( xPropSet.is() ) { OUString aPrefferedName = - OUString::number(static_cast<sal_Int32>(Color(aXGradient.GetColorStops().front().getStopColor()))) - + OUString::number(static_cast<sal_Int32>(Color(aXGradient.GetColorStops().back().getStopColor()))) - + OUString::number(static_cast<sal_Int32>(aXGradient.GetAngle().get())); + OUString::number(static_cast<sal_Int32>(Color(aBGradient.GetColorStops().front().getStopColor()))) + + OUString::number(static_cast<sal_Int32>(Color(aBGradient.GetColorStops().back().getStopColor()))) + + OUString::number(static_cast<sal_Int32>(aBGradient.GetAngle().get())); OUString aNewName = PropertyHelper::addGradientUniqueNameToTable(css::uno::Any(aGradient), xChartModel, diff --git a/cui/source/inc/cuitabarea.hxx b/cui/source/inc/cuitabarea.hxx index 2db104f53c8c..64ec01ef5abf 100644 --- a/cui/source/inc/cuitabarea.hxx +++ b/cui/source/inc/cuitabarea.hxx @@ -170,7 +170,7 @@ class SvxTransparenceTabPage : public SfxTabPage std::unique_ptr<weld::CustomWeld> m_xCtlXRectPreview; // MCGR: Preserve in-between ColorStops until we have an UI to edit these - basegfx::ColorStops maColorStops; + basegfx::BColorStops maColorStops; DECL_LINK(ClickTransOffHdl_Impl, weld::Toggleable&, void); DECL_LINK(ClickTransLinearHdl_Impl, weld::Toggleable&, void); @@ -188,7 +188,7 @@ class SvxTransparenceTabPage : public SfxTabPage void InvalidatePreview (bool bEnable = true ); // MCGR: Preserve in-between ColorStops until we have an UI to edit these - basegfx::ColorStops createColorStops(); + basegfx::BColorStops createColorStops(); public: SvxTransparenceTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rInAttrs); @@ -368,7 +368,7 @@ private: SfxItemSet& m_rXFSet; // MCGR: Preserve in-between ColorStops until we have an UI to edit these - basegfx::ColorStops m_aColorStops; + basegfx::BColorStops m_aColorStops; SvxXRectPreview m_aCtlPreview; std::unique_ptr<weld::ComboBox> m_xLbGradientType; @@ -410,7 +410,7 @@ private: sal_Int32 SearchGradientList(std::u16string_view rGradientName); // MCGR: Preserve in-between ColorStops until we have an UI to edit these - basegfx::ColorStops createColorStops(); + basegfx::BColorStops createColorStops(); public: SvxGradientTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rInAttrs); diff --git a/cui/source/tabpages/tpgradnt.cxx b/cui/source/tabpages/tpgradnt.cxx index 57eed4dd4ce5..7f6630747a42 100644 --- a/cui/source/tabpages/tpgradnt.cxx +++ b/cui/source/tabpages/tpgradnt.cxx @@ -84,8 +84,7 @@ SvxGradientTabPage::SvxGradientTabPage(weld::Container* pPage, weld::DialogContr // setting the output device m_rXFSet.Put( XFillStyleItem(drawing::FillStyle_GRADIENT) ); - // XGradient() default already creates [COL_BLACK, COL_WHITE] as defaults - m_rXFSet.Put( XFillGradientItem(OUString(), XGradient())); + m_rXFSet.Put( XFillGradientItem(OUString(), basegfx::BGradient())); m_aCtlPreview.SetAttributes(m_aXFillAttr.GetItemSet()); // set handler @@ -185,18 +184,18 @@ DeactivateRC SvxGradientTabPage::DeactivatePage( SfxItemSet* _pSet ) bool SvxGradientTabPage::FillItemSet( SfxItemSet* rSet ) { - std::unique_ptr<XGradient> pXGradient; + std::unique_ptr<basegfx::BGradient> pBGradient; size_t nPos = m_xGradientLB->IsNoSelection() ? VALUESET_ITEM_NOTFOUND : m_xGradientLB->GetSelectItemPos(); if( nPos != VALUESET_ITEM_NOTFOUND ) { - pXGradient.reset(new XGradient( m_pGradientList->GetGradient( static_cast<sal_uInt16>(nPos) )->GetGradient() )); + pBGradient.reset(new basegfx::BGradient( m_pGradientList->GetGradient( static_cast<sal_uInt16>(nPos) )->GetGradient() )); OUString aString = m_xGradientLB->GetItemText( m_xGradientLB->GetSelectedItemId() ); - rSet->Put( XFillGradientItem( aString, *pXGradient ) ); + rSet->Put( XFillGradientItem( aString, *pBGradient ) ); } else // gradient was passed (unidentified) { - pXGradient.reset(new XGradient( + pBGradient.reset(new basegfx::BGradient( createColorStops(), static_cast<css::awt::GradientStyle>(m_xLbGradientType->get_active()), Degree10(static_cast<sal_Int16>(m_xMtrAngle->get_value(FieldUnit::NONE) * 10)), // should be changed in resource @@ -206,14 +205,14 @@ bool SvxGradientTabPage::FillItemSet( SfxItemSet* rSet ) static_cast<sal_uInt16>(m_xMtrColorFrom->get_value(FieldUnit::NONE)), static_cast<sal_uInt16>(m_xMtrColorTo->get_value(FieldUnit::NONE)), static_cast<sal_uInt16>(m_xMtrIncrement->get_value()) )); - rSet->Put( XFillGradientItem( OUString(), *pXGradient ) ); + rSet->Put( XFillGradientItem( OUString(), *pBGradient ) ); } sal_uInt16 nValue = 0; if (!m_xCbIncrement->get_active()) nValue = m_xMtrIncrement->get_value(); - assert( pXGradient && "XGradient could not be created" ); + assert( pBGradient && "basegfx::BGradient could not be created" ); rSet->Put( XFillStyleItem( drawing::FillStyle_GRADIENT ) ); rSet->Put( XGradientStepCountItem( nValue ) ); return true; @@ -294,7 +293,7 @@ void SvxGradientTabPage::ModifiedHdl_Impl( void const * pControl ) css::awt::GradientStyle eXGS = static_cast<css::awt::GradientStyle>(m_xLbGradientType->get_active()); - XGradient aXGradient( + basegfx::BGradient aBGradient( createColorStops(), eXGS, Degree10(static_cast<sal_Int16>(m_xMtrAngle->get_value(FieldUnit::NONE) * 10)), // should be changed in resource @@ -315,7 +314,7 @@ void SvxGradientTabPage::ModifiedHdl_Impl( void const * pControl ) m_rXFSet.Put( XGradientStepCountItem( nValue ) ); // displaying in XOutDev - m_rXFSet.Put( XFillGradientItem( OUString(), aXGradient ) ); + m_rXFSet.Put( XFillGradientItem( OUString(), aBGradient ) ); m_aCtlPreview.SetAttributes(m_aXFillAttr.GetItemSet()); m_aCtlPreview.Invalidate(); } @@ -361,7 +360,7 @@ IMPL_LINK_NOARG(SvxGradientTabPage, ClickAddHdl_Impl, weld::Button&, void) if( !nError ) { - XGradient aXGradient( + basegfx::BGradient aBGradient( createColorStops(), static_cast<css::awt::GradientStyle>(m_xLbGradientType->get_active()), Degree10(static_cast<sal_Int16>(m_xMtrAngle->get_value(FieldUnit::NONE) * 10)), // should be changed in resource @@ -372,7 +371,7 @@ IMPL_LINK_NOARG(SvxGradientTabPage, ClickAddHdl_Impl, weld::Button&, void) static_cast<sal_uInt16>(m_xMtrColorTo->get_value(FieldUnit::NONE)), static_cast<sal_uInt16>(m_xMtrIncrement->get_value()) ); - m_pGradientList->Insert(std::make_unique<XGradientEntry>(aXGradient, aName), nCount); + m_pGradientList->Insert(std::make_unique<XGradientEntry>(aBGradient, aName), nCount); sal_Int32 nId = m_xGradientLB->GetItemId(nCount - 1); //calculate the last ID BitmapEx aBitmap = m_pGradientList->GetBitmapForPreview( nCount, m_xGradientLB->GetIconSize() ); @@ -401,7 +400,7 @@ IMPL_LINK_NOARG(SvxGradientTabPage, ClickModifyHdl_Impl, weld::Button&, void) OUString aName( m_pGradientList->GetGradient( static_cast<sal_uInt16>(nPos) )->GetName() ); - XGradient aXGradient( + basegfx::BGradient aBGradient( createColorStops(), static_cast<css::awt::GradientStyle>(m_xLbGradientType->get_active()), Degree10(static_cast<sal_Int16>(m_xMtrAngle->get_value(FieldUnit::NONE) * 10)), // should be changed in resource @@ -412,7 +411,7 @@ IMPL_LINK_NOARG(SvxGradientTabPage, ClickModifyHdl_Impl, weld::Button&, void) static_cast<sal_uInt16>(m_xMtrColorTo->get_value(FieldUnit::NONE)), static_cast<sal_uInt16>(m_xMtrIncrement->get_value()) ); - m_pGradientList->Replace(std::make_unique<XGradientEntry>(aXGradient, aName), nPos); + m_pGradientList->Replace(std::make_unique<XGradientEntry>(aBGradient, aName), nPos); BitmapEx aBitmap = m_pGradientList->GetBitmapForPreview( static_cast<sal_uInt16>(nPos), m_xGradientLB->GetIconSize() ); m_xGradientLB->RemoveItem( nId ); @@ -498,11 +497,11 @@ IMPL_LINK_NOARG(SvxGradientTabPage, ChangeGradientHdl, ValueSet*, void) void SvxGradientTabPage::ChangeGradientHdl_Impl() { - std::unique_ptr<XGradient> pGradient; + std::unique_ptr<basegfx::BGradient> pGradient; size_t nPos = m_xGradientLB->GetSelectItemPos(); if( nPos != VALUESET_ITEM_NOTFOUND ) - pGradient.reset(new XGradient( m_pGradientList->GetGradient( static_cast<sal_uInt16>( nPos ) )->GetGradient() )); + pGradient.reset(new basegfx::BGradient( m_pGradientList->GetGradient( static_cast<sal_uInt16>( nPos ) )->GetGradient() )); else { if( const XFillStyleItem* pFillStyleItem = m_rOutAttrs.GetItemIfSet( GetWhich( XATTR_FILLSTYLE ) ) ) @@ -511,7 +510,7 @@ void SvxGradientTabPage::ChangeGradientHdl_Impl() if( ( drawing::FillStyle_GRADIENT == pFillStyleItem->GetValue() ) && ( pGradientItem = m_rOutAttrs.GetItemIfSet( GetWhich( XATTR_FILLGRADIENT ) ) ) ) { - pGradient.reset(new XGradient( pGradientItem->GetGradientValue() )); + pGradient.reset(new basegfx::BGradient( pGradientItem->GetGradientValue() )); } } if( !pGradient ) @@ -519,7 +518,7 @@ void SvxGradientTabPage::ChangeGradientHdl_Impl() sal_uInt16 nPosition = m_xGradientLB->GetItemId(0); m_xGradientLB->SelectItem( nPosition ); if( nPosition != 0 ) - pGradient.reset(new XGradient( m_pGradientList->GetGradient( 0 )->GetGradient() )); + pGradient.reset(new basegfx::BGradient( m_pGradientList->GetGradient( 0 )->GetGradient() )); } } @@ -551,7 +550,7 @@ void SvxGradientTabPage::ChangeGradientHdl_Impl() // MCGR: preserve in-between ColorStops if given if (pGradient->GetColorStops().size() > 2) - m_aColorStops = basegfx::ColorStops(pGradient->GetColorStops().begin() + 1, pGradient->GetColorStops().end() - 1); + m_aColorStops = basegfx::BColorStops(pGradient->GetColorStops().begin() + 1, pGradient->GetColorStops().end() - 1); else m_aColorStops.clear(); @@ -638,9 +637,9 @@ sal_Int32 SvxGradientTabPage::SearchGradientList(std::u16string_view rGradientNa return nPos; } -basegfx::ColorStops SvxGradientTabPage::createColorStops() +basegfx::BColorStops SvxGradientTabPage::createColorStops() { - basegfx::ColorStops aColorStops; + basegfx::BColorStops aColorStops; aColorStops.emplace_back(0.0, m_xLbColorFrom->GetSelectEntryColor().getBColor()); diff --git a/cui/source/tabpages/tptrans.cxx b/cui/source/tabpages/tptrans.cxx index ef604e6470d5..ce77d61d2fc7 100644 --- a/cui/source/tabpages/tptrans.cxx +++ b/cui/source/tabpages/tptrans.cxx @@ -119,7 +119,7 @@ void SvxTransparenceTabPage::ModifiedTrgrHdl_Impl(const weld::ComboBox* pControl } // preview - XGradient aTmpGradient( + basegfx::BGradient aTmpGradient( createColorStops(), static_cast<css::awt::GradientStyle>(m_xLbTrgrGradientType->get_active()), Degree10(static_cast<sal_Int16>(m_xMtrTrgrAngle->get_value(FieldUnit::DEGREE)) * 10), @@ -287,7 +287,7 @@ bool SvxTransparenceTabPage::FillItemSet(SfxItemSet* rAttrs) || m_xMtrTrgrStartValue->get_value_changed_from_saved() || m_xMtrTrgrEndValue->get_value_changed_from_saved()) { - XGradient aTmpGradient( + basegfx::BGradient aTmpGradient( createColorStops(), static_cast<css::awt::GradientStyle>(m_xLbTrgrGradientType->get_active()), Degree10(static_cast<sal_Int16>(m_xMtrTrgrAngle->get_value(FieldUnit::DEGREE)) * 10), @@ -317,9 +317,9 @@ bool SvxTransparenceTabPage::FillItemSet(SfxItemSet* rAttrs) // disable unused XFillFloatTransparenceItem if(bSwitchOffGradient && (bGradActive || bGradUsed)) { - // XGradient() default already creates [COL_BLACK, COL_WHITE] with same defaults - // XGradient() default also sets the Start/EndIntensity to 100 already - XGradient aGrad; + // basegfx::BGradient() default already creates [COL_BLACK, COL_WHITE] with same defaults + // basegfx::BGradient() default also sets the Start/EndIntensity to 100 already + basegfx::BGradient aGrad; XFillFloatTransparenceItem aItem(aGrad); aItem.SetEnabled(false); @@ -355,7 +355,7 @@ void SvxTransparenceTabPage::Reset(const SfxItemSet* rAttrs) pLinearItem = &rAttrs->Get(XATTR_FILLTRANSPARENCE); // transparence gradient - const XGradient& rGradient = pGradientItem->GetGradientValue(); + const basegfx::BGradient& rGradient = pGradientItem->GetGradientValue(); css::awt::GradientStyle eXGS(rGradient.GetGradientStyle()); m_xLbTrgrGradientType->set_active(sal::static_int_cast< sal_Int32 >(eXGS)); m_xMtrTrgrAngle->set_value(rGradient.GetAngle().get() / 10, FieldUnit::DEGREE); @@ -369,7 +369,7 @@ void SvxTransparenceTabPage::Reset(const SfxItemSet* rAttrs) // MCGR: preserve in-between ColorStops if given if (rGradient.GetColorStops().size() > 2) - maColorStops = basegfx::ColorStops(rGradient.GetColorStops().begin() + 1, rGradient.GetColorStops().end() - 1); + maColorStops = basegfx::BColorStops(rGradient.GetColorStops().begin() + 1, rGradient.GetColorStops().end() - 1); else maColorStops.clear(); @@ -508,9 +508,9 @@ void SvxTransparenceTabPage::InvalidatePreview (bool bEnable) } } -basegfx::ColorStops SvxTransparenceTabPage::createColorStops() +basegfx::BColorStops SvxTransparenceTabPage::createColorStops() { - basegfx::ColorStops aColorStops; + basegfx::BColorStops aColorStops; const sal_uInt8 nStartCol(static_cast<sal_uInt8>((static_cast<sal_uInt16>(m_xMtrTrgrStartValue->get_value(FieldUnit::PERCENT)) * 255) / 100)); const sal_uInt8 nEndCol(static_cast<sal_uInt8>((static_cast<sal_uInt16>(m_xMtrTrgrEndValue->get_value(FieldUnit::PERCENT)) * 255) / 100)); diff --git a/drawinglayer/inc/texture/texture.hxx b/drawinglayer/inc/texture/texture.hxx index 01d3ec5c64c5..5128a30cf2a8 100644 --- a/drawinglayer/inc/texture/texture.hxx +++ b/drawinglayer/inc/texture/texture.hxx @@ -49,21 +49,18 @@ namespace drawinglayer::texture basegfx::ODFGradientInfo maGradientInfo; basegfx::B2DRange maDefinitionRange; sal_uInt32 mnRequestedSteps; - basegfx::ColorStops mnColorStops; + basegfx::BColorStops mnColorStops; double mfBorder; // provide a single buffer entry used for gradient texture // mapping, see ::modifyBColor implementations - mutable basegfx::ColorStopRange maLastColorStopRange; - - // check if we need last-ColorStop-correction - bool checkPenultimate(); + mutable basegfx::BColorStops::BColorStopRange maLastColorStopRange; public: GeoTexSvxGradient( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder); virtual ~GeoTexSvxGradient() override; @@ -86,7 +83,7 @@ namespace drawinglayer::texture const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fAngle); virtual ~GeoTexSvxGradientLinear() override; @@ -106,7 +103,7 @@ namespace drawinglayer::texture const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fAngle); virtual ~GeoTexSvxGradientAxial() override; @@ -122,7 +119,7 @@ namespace drawinglayer::texture GeoTexSvxGradientRadial( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY); @@ -139,7 +136,7 @@ namespace drawinglayer::texture GeoTexSvxGradientElliptical( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, @@ -157,7 +154,7 @@ namespace drawinglayer::texture GeoTexSvxGradientSquare( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, @@ -175,7 +172,7 @@ namespace drawinglayer::texture GeoTexSvxGradientRect( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fOffsetX, double fOffsetY, diff --git a/drawinglayer/qa/unit/vclpixelprocessor2d.cxx b/drawinglayer/qa/unit/vclpixelprocessor2d.cxx index 6ccf1e7c55ab..7f59e7e3edd0 100644 --- a/drawinglayer/qa/unit/vclpixelprocessor2d.cxx +++ b/drawinglayer/qa/unit/vclpixelprocessor2d.cxx @@ -16,8 +16,10 @@ #include <tools/stream.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/processor2d/baseprocessor2d.hxx> #include <drawinglayer/processor2d/processor2dtools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/utils/gradienttools.hxx> using namespace drawinglayer; @@ -58,16 +60,59 @@ public: processor2d::createProcessor2DFromOutputDevice(*device, view)); CPPUNIT_ASSERT(processor); + // I stumbled over this when hunting another problem, but I have to correct + // this: This test does test something that is not supported. It seems to be + // based on the *misunderstanding* that in the version of the constructor of + // FillGradientPrimitive2D (and similar others) with two ranges the 2nd + // B2DRange parameter 'OutputRange' is a 'clipping' parameter. This is *not* + // the case --- it is in fact the *contrary*, it is there to *extend* the + // usual definition/paintRange of a gradient: + // It was originally needed to correctly display TextFrames (TF) in Writer: If you + // have a TF in SW filled with a gradient and that TF has sub-frames, it inhertits + // the gradient fill. Since you can freely move those sub-TFs even outside the + // parent TF there has to be a way to not only paint gradients in ther definition + // range (classical, all DrawObjects do that), but extended from that. This is + // needed e.g. for linerar gradients, but - dependent of e.g. the center settings - + // also for all other ones, all can have geometry 'outside' the DefinitionRange. + // This is now also used in various other locations which is proof that this is + // useful and needed. It is possible to see that basic history/reason for this + // parameter by following the git history and why and under which circumstances + // that parameter was originally added. Other hints are: It is *not* named + // 'ClipRange'. Using a B2DRange to define a ClipRange topology would be bad due + // to not being transformable, a PolyPolygon would be used in that case. Using as + // clipping mechanism would offer a 2nd pinciple to add clipping for primitives + // besides MaskPrimitive2D - always bad style in a sub-system. A quick look + // on it's usages gives hints, too. + // This means that when defining a outputRange tat resides completely *inside* + // the definitionRange *no change* at all is done by definition since this does + // not *extend* the target area of the gradient paint region at all. If an + // implementation does clip and limit output to 'outputRange' that should do no + // harm, but is not the expected/reliable way to paint primitives clipped. + // That's why all DrawObjects with gradient fill (and other fills do the same) + // embed the fill that is defined for a range (usually the BoundRange of a + // PolyPolygon) in a MaskPrimitive2D defined by the outline PolyPolygon of the + // shape. Nothing speaks against renderers detecting that combination and do + // something optimized if they want to, especially SDPRs, but this is not + // required. The standard embedded clipping of the mplementations of the + // MaskPrimitive2D do the right thing. + // This test intends to paint the lower part of a gradient, so define the + // gradient for the full target range and embed it to a MaskPrimitive2D + // defining the lower part of that area to do that. + basegfx::B2DRange definitionRange(0, 0, 100, 200); basegfx::B2DRange outputRange(0, 100, 100, 200); // Paint only lower half of the gradient. - attribute::FillGradientAttribute attributes( - css::awt::GradientStyle_LINEAR, 0, 0, 0, 0, - basegfx::utils::createColorStopsFromStartEndColor(COL_WHITE.getBColor(), - COL_BLACK.getBColor())); - rtl::Reference<primitive2d::FillGradientPrimitive2D> gradientPrimitive( - new primitive2d::FillGradientPrimitive2D(outputRange, definitionRange, attributes)); - primitive2d::Primitive2DContainer primitives; - primitives.push_back(primitive2d::Primitive2DReference(gradientPrimitive)); + + const primitive2d::Primitive2DContainer primitives{ + rtl::Reference<primitive2d::MaskPrimitive2D>(new primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(outputRange)), + primitive2d::Primitive2DContainer{ + rtl::Reference<primitive2d::FillGradientPrimitive2D>( + new primitive2d::FillGradientPrimitive2D( + definitionRange, attribute::FillGradientAttribute( + css::awt::GradientStyle_LINEAR, 0, 0, 0, 0, + basegfx::BColorStops(COL_WHITE.getBColor(), + COL_BLACK.getBColor())))) })) + }; processor->process(primitives); exportDevice("test-tdf139000.png", device); diff --git a/drawinglayer/source/attribute/fillgradientattribute.cxx b/drawinglayer/source/attribute/fillgradientattribute.cxx index 0233195113af..e02fdd4a5dad 100644 --- a/drawinglayer/source/attribute/fillgradientattribute.cxx +++ b/drawinglayer/source/attribute/fillgradientattribute.cxx @@ -30,7 +30,7 @@ namespace drawinglayer::attribute double mfOffsetX; double mfOffsetY; double mfAngle; - basegfx::ColorStops maColorStops; + basegfx::BColorStops maColorStops; css::awt::GradientStyle meStyle; sal_uInt16 mnSteps; @@ -40,7 +40,7 @@ namespace drawinglayer::attribute double fOffsetX, double fOffsetY, double fAngle, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, sal_uInt16 nSteps) : mfBorder(fBorder), mfOffsetX(fOffsetX), @@ -57,7 +57,7 @@ namespace drawinglayer::attribute // This is what the usages of this in primitives need. // Since FillGradientAttribute is read-only doing this // once here in the constructor is sufficient - basegfx::utils::sortAndCorrectColorStops(maColorStops); + maColorStops.sortAndCorrect(); // sortAndCorrectColorStops is rigid and can return // an empty result. To keep things simple, add a single @@ -87,18 +87,9 @@ namespace drawinglayer::attribute double getOffsetX() const { return mfOffsetX; } double getOffsetY() const { return mfOffsetY; } double getAngle() const { return mfAngle; } - const basegfx::ColorStops& getColorStops() const { return maColorStops; } + const basegfx::BColorStops& getColorStops() const { return maColorStops; } sal_uInt16 getSteps() const { return mnSteps; } - bool hasSingleColor() const - { - // No entry (should not happen, see comments for startColor above) - // or single entry -> no gradient. - // No need to check for all-the-same color since this is checked/done - // in the constructor already, see there - return maColorStops.size() < 2; - } - bool operator==(const ImpFillGradientAttribute& rCandidate) const { return (getStyle() == rCandidate.getStyle() @@ -126,7 +117,7 @@ namespace drawinglayer::attribute double fOffsetX, double fOffsetY, double fAngle, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, sal_uInt16 nSteps) : mpFillGradientAttribute(ImpFillGradientAttribute( eStyle, fBorder, fOffsetX, fOffsetY, fAngle, rColorStops, nSteps)) @@ -149,11 +140,6 @@ namespace drawinglayer::attribute return mpFillGradientAttribute.same_object(theGlobalDefault()); } - bool FillGradientAttribute::hasSingleColor() const - { - return mpFillGradientAttribute->hasSingleColor(); - } - // MCGR: Check if rendering cannot be handled by old vcl stuff bool FillGradientAttribute::cannotBeHandledByVCL() const { @@ -202,7 +188,7 @@ namespace drawinglayer::attribute return rCandidate.mpFillGradientAttribute == mpFillGradientAttribute; } - const basegfx::ColorStops& FillGradientAttribute::getColorStops() const + const basegfx::BColorStops& FillGradientAttribute::getColorStops() const { return mpFillGradientAttribute->getColorStops(); } diff --git a/drawinglayer/source/primitive3d/textureprimitive3d.cxx b/drawinglayer/source/primitive3d/textureprimitive3d.cxx index ebae584e9dbf..ceeca0489487 100644 --- a/drawinglayer/source/primitive3d/textureprimitive3d.cxx +++ b/drawinglayer/source/primitive3d/textureprimitive3d.cxx @@ -95,9 +95,9 @@ namespace drawinglayer::primitive3d const basegfx::BColor aGray(getTransparence(), getTransparence(), getTransparence()); // create ColorStops with StartColor == EndColor == aGray - const basegfx::ColorStops aColorStops { - basegfx::ColorStop(0.0, aGray), - basegfx::ColorStop(1.0, aGray) }; + const basegfx::BColorStops aColorStops { + basegfx::BColorStop(0.0, aGray), + basegfx::BColorStop(1.0, aGray) }; const attribute::FillGradientAttribute aFillGradient(css::awt::GradientStyle_LINEAR, 0.0, 0.0, 0.0, 0.0, aColorStops); const Primitive3DReference xRef(new TransparenceTexturePrimitive3D(aFillGradient, getChildren(), getTextureSize())); diff --git a/drawinglayer/source/processor3d/defaultprocessor3d.cxx b/drawinglayer/source/processor3d/defaultprocessor3d.cxx index 20d81871dbd7..0d07b0a5c343 100644 --- a/drawinglayer/source/processor3d/defaultprocessor3d.cxx +++ b/drawinglayer/source/processor3d/defaultprocessor3d.cxx @@ -61,8 +61,9 @@ namespace drawinglayer::processor3d const basegfx::B2DRange aOutlineRange(0.0, 0.0, rPrimitive.getTextureSize().getX(), rPrimitive.getTextureSize().getY()); const css::awt::GradientStyle aGradientStyle(rFillGradient.getStyle()); std::shared_ptr< texture::GeoTexSvx > pNewTex; + basegfx::BColor aSingleColor; - if(!rFillGradient.hasSingleColor()) + if (!rFillGradient.getColorStops().isSingleColor(aSingleColor)) { switch(aGradientStyle) { @@ -147,8 +148,7 @@ namespace drawinglayer::processor3d else { // only one color, so no real gradient -> use simple texture - const basegfx::BColor aStart(rFillGradient.getColorStops().front().getStopColor()); - pNewTex = std::make_shared<texture::GeoTexSvxMono>(aStart, 1.0 - aStart.luminance()); + pNewTex = std::make_shared<texture::GeoTexSvxMono>(aSingleColor, 1.0 - aSingleColor.luminance()); mbSimpleTextureActive = true; } diff --git a/drawinglayer/source/texture/texture.cxx b/drawinglayer/source/texture/texture.cxx index eb9df6469225..5176838d610e 100644 --- a/drawinglayer/source/texture/texture.cxx +++ b/drawinglayer/source/texture/texture.cxx @@ -73,7 +73,7 @@ namespace drawinglayer::texture GeoTexSvxGradient::GeoTexSvxGradient( const basegfx::B2DRange& rDefinitionRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder) : maDefinitionRange(rDefinitionRange) , mnRequestedSteps(nRequestedSteps) @@ -99,47 +99,11 @@ namespace drawinglayer::texture && mfBorder == pCompare->mfBorder); } - bool GeoTexSvxGradient::checkPenultimate() - { - // not needed when no ColorStops - if (mnColorStops.empty()) - return false; - - // not needed when last ColorStop at the end or outside - if (basegfx::fTools::moreOrEqual(mnColorStops.back().getStopOffset(), 1.0)) - return false; - - // get penultimate entry - const auto penultimate(mnColorStops.rbegin() + 1); - - // if there is none, we need no correction and are done - if (penultimate == mnColorStops.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(mnColorStops.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 (mnColorStops.back().getStopColor() == penultimate->getStopColor()) - return false; - - // Here we need to temporarily add a ColorStop entry with the - // same color as the last entry to correctly 'close' the - // created gradient geometry. - // The simplest way is to temporarily add an entry to the local - // ColorStops for this at 1.0 (using same color) - mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); - return true; - } - GeoTexSvxGradientLinear::GeoTexSvxGradientLinear( const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, + const basegfx::BColorStops& rColorStops, double fBorder, double fAngle) : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder) @@ -180,7 +144,17 @@ namespace drawinglayer::texture return; // check if we need last-ColorStop-correction - const bool bPenultimateUsed(checkPenultimate()); + const bool bPenultimateUsed(mnColorStops.checkPenultimate()); + + if (bPenultimateUsed) + { + // Here we need to temporarily add a ColorStop entry with the + // same color as the last entry to correctly 'close' the + // created gradient geometry. + // The simplest way is to temporarily add an entry to the local + // ColorStops for this at 1.0 (using same color) + mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor()); + } // prepare unit range transform basegfx::B2DHomMatrix aPattern; @@ -200,7 +174,7 @@ namespace drawinglayer::texture const double fOffsetStart(cs_l->getStopOffset()); const double fOffsetEnd(cs_r->getStopOffset()); - // same offset, empty ColorStopRange, continue with next step + // same offset, empty BColorStopRange, continue with next step if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd)) continue; @@ -249,7 +223,10 @@ namespace drawinglayer::texture } if (bPenultimateUsed) + { + // correct temporary change mnColorStops.pop_back(); + } } void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const @@ -267,14 +244,14 @@ namespace drawinglayer::texture // texture-back-transform X/Y -> t [0.0..1.0] and determine color const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo)); - rBColor = basegfx::utils::modifyBColor(mnColorStops, fScaler, mnRequestedSteps, maLastColorStopRange); + rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange); } GeoTexSvxGradientAxial::GeoTexSvxGradientAxial( const basegfx::B2DRange& rDefinitionRange, const basegfx::B2DRange& rOutputRange, sal_uInt32 nRequestedSteps, - const basegfx::ColorStops& rColorStops, ... etc. - the rest is truncated