diff options
Diffstat (limited to 'basegfx/source/tools/gradienttools.cxx')
-rw-r--r-- | basegfx/source/tools/gradienttools.cxx | 361 |
1 files changed, 323 insertions, 38 deletions
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx index 3605d8fe0be0..8f3e8ae83c06 100644 --- a/basegfx/source/tools/gradienttools.cxx +++ b/basegfx/source/tools/gradienttools.cxx @@ -21,6 +21,11 @@ #include <basegfx/point/b2dpoint.hxx> #include <basegfx/range/b2drange.hxx> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <com/sun/star/awt/Gradient2.hpp> +#include <osl/endian.h> + +#include <algorithm> +#include <cmath> namespace basegfx { @@ -28,7 +33,7 @@ namespace basegfx { return getTextureTransform() == rODFGradientInfo.getTextureTransform() && getAspectRatio() == rODFGradientInfo.getAspectRatio() - && getSteps() == rODFGradientInfo.getSteps(); + && getRequestedSteps() == rODFGradientInfo.getRequestedSteps(); } const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const @@ -135,7 +140,7 @@ namespace basegfx // add object expansion if(bCircular) { - const double fOriginalDiag(sqrt((fTargetSizeX * fTargetSizeX) + (fTargetSizeY * fTargetSizeY))); + const double fOriginalDiag(std::hypot(fTargetSizeX, fTargetSizeY)); fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0; fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0; @@ -144,10 +149,10 @@ namespace basegfx } else { - fTargetOffsetX -= (0.4142 / 2.0 ) * fTargetSizeX; - fTargetOffsetY -= (0.4142 / 2.0 ) * fTargetSizeY; - fTargetSizeX = 1.4142 * fTargetSizeX; - fTargetSizeY = 1.4142 * fTargetSizeY; + fTargetOffsetX -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeX; + fTargetOffsetY -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeY; + fTargetSizeX = M_SQRT2 * fTargetSizeX; + fTargetSizeY = M_SQRT2 * fTargetSizeY; } const double fHalfBorder((1.0 - fBorder) * 0.5); @@ -260,6 +265,233 @@ namespace basegfx namespace utils { + /* Tooling method to extract data from given BGradient + to ColorStops, doing some corrections, partially based + on given SingleColor */ + void prepareColorStops( + const basegfx::BGradient& rGradient, + BColorStops& rColorStops, + BColor& rSingleColor) + { + if (rGradient.GetColorStops().isSingleColor(rSingleColor)) + { + // when single color, preserve value in rSingleColor + // and clear the ColorStops, done. + rColorStops.clear(); + return; + } + + const bool bAdaptStartEndIntensity(100 != rGradient.GetStartIntens() || 100 != rGradient.GetEndIntens()); + const bool bAdaptBorder(0 != rGradient.GetBorder()); + + if (!bAdaptStartEndIntensity && !bAdaptBorder) + { + // 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 (bAdaptBorder) + { + 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. */ + void synchronizeColorStops( + BColorStops& rColorStops, + BColorStops& rAlphaStops, + const BColor& rSingleColor, + const BColor& rSingleAlpha) + { + if (rColorStops.empty()) + { + if (rAlphaStops.empty()) + { + // no AlphaStops and no ColorStops + // create two-stop fallbacks for both + rColorStops = BColorStops { + BColorStop(0.0, rSingleColor), + BColorStop(1.0, rSingleColor) }; + rAlphaStops = BColorStops { + BColorStop(0.0, rSingleAlpha), + BColorStop(1.0, rSingleAlpha) }; + } + else + { + // AlphaStops but no ColorStops + // create fallback synched with existing AlphaStops + for (const auto& cand : rAlphaStops) + { + rColorStops.emplace_back(cand.getStopOffset(), rSingleColor); + } + } + + // preparations complete, we are done + return; + } + else if (rAlphaStops.empty()) + { + // ColorStops but no AlphaStops + // create fallback AlphaStops synched with existing ColorStops using SingleAlpha + for (const auto& cand : rColorStops) + { + rAlphaStops.emplace_back(cand.getStopOffset(), rSingleAlpha); + } + + // preparations complete, we are done + return; + } + + // here we have ColorStops and AlphaStops not empty. Check if we need to + // synchronize both or if they are already usable/in a synched state so + // that they have same count and same StopOffsets + bool bNeedToSyncronize(rColorStops.size() != rAlphaStops.size()); + + if (!bNeedToSyncronize) + { + // check for same StopOffsets + BColorStops::const_iterator aCurrColor(rColorStops.begin()); + BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin()); + + while (!bNeedToSyncronize && + aCurrColor != rColorStops.end() && + aCurrAlpha != rAlphaStops.end()) + { + if (fTools::equal(aCurrColor->getStopOffset(), aCurrAlpha->getStopOffset())) + { + aCurrColor++; + aCurrAlpha++; + } + else + { + bNeedToSyncronize = true; + } + } + } + + if (bNeedToSyncronize) + { + // synchronize sizes & StopOffsets + 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 { + const bool bColor(aCurrColor != rColorStops.end()); + const bool bAlpha(aCurrAlpha != rAlphaStops.end()); + + if (bColor && bAlpha) + { + const double fColorOff(aCurrColor->getStopOffset()); + const double fAlphaOff(aCurrAlpha->getStopOffset()); + + if (fTools::less(fColorOff, fAlphaOff)) + { + // copy color, create alpha + aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor()); + 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, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange)); + aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor()); + bRealChange = true; + aCurrAlpha++; + } + else + { + // equal: copy both, advance + aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor()); + aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor()); + aCurrColor++; + aCurrAlpha++; + } + } + else if (bColor) + { + const double fColorOff(aCurrColor->getStopOffset()); + aNewAlpha.emplace_back(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange)); + aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor()); + bRealChange = true; + aCurrColor++; + } + else if (bAlpha) + { + const double fAlphaOff(aCurrAlpha->getStopOffset()); + aNewColor.emplace_back(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange)); + aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor()); + bRealChange = true; + aCurrAlpha++; + } + else + { + // no more input, break do..while loop + break; + } + } + while(true); + + if (bRealChange) + { + // copy on 'real' change, that means data was added. + // This should always be the cease and should have been + // detected as such above, see bNeedToSyncronize + rColorStops = aNewColor; + rAlphaStops = aNewAlpha; // MCGR: tdf#155537 used wrong result here + } + } + } + + sal_uInt32 calculateNumberOfSteps( + sal_uInt32 nRequestedSteps, + const BColor& rStart, + const BColor& rEnd) + { + const sal_uInt32 nMaxSteps(sal_uInt32((rStart.getMaximumDistance(rEnd) * 127.5) + 0.5)); + + if (0 == nRequestedSteps) + { + nRequestedSteps = nMaxSteps; + } + + if(nRequestedSteps > nMaxSteps) + { + nRequestedSteps = nMaxSteps; + } + + return std::max(sal_uInt32(1), nRequestedSteps); + } + ODFGradientInfo createLinearODFGradientInfo( const B2DRange& rTargetArea, sal_uInt32 nSteps, @@ -355,7 +587,7 @@ namespace basegfx { const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); - // Ignore Y, this is not needed at all for Y-Oriented gradients + // Ignore X, this is not needed at all for Y-Oriented gradients // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0) // { // return 0.0; @@ -371,13 +603,6 @@ namespace basegfx return 1.0; // end value for outside } - const sal_uInt32 nSteps(rGradInfo.getSteps()); - - if(nSteps) - { - return floor(aCoor.getY() * nSteps) / double(nSteps - 1); - } - return aCoor.getY(); } @@ -385,7 +610,7 @@ namespace basegfx { const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); - // Ignore Y, this is not needed at all for Y-Oriented gradients + // Ignore X, this is not needed at all for Y-Oriented gradients //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0) //{ // return 0.0; @@ -398,17 +623,22 @@ namespace basegfx return 1.0; // use end value when outside in Y } - const sal_uInt32 nSteps(rGradInfo.getSteps()); + return fAbsY; + } - if(nSteps) + double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) + { + const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); + + if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0) { - return floor(fAbsY * nSteps) / double(nSteps - 1); + return 0.0; } - return fAbsY; + return 1.0 - std::hypot(aCoor.getX(), aCoor.getY()); } - double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) + double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) { const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); @@ -417,22 +647,26 @@ namespace basegfx return 0.0; } - const double t(1.0 - sqrt(aCoor.getX() * aCoor.getX() + aCoor.getY() * aCoor.getY())); - const sal_uInt32 nSteps(rGradInfo.getSteps()); + double fAspectRatio(rGradInfo.getAspectRatio()); + double t(1.0); - if(nSteps && t < 1.0) + // MCGR: Similar to getRectangularGradientAlpha (please + // see there) we need to use aspect ratio here. Due to + // initEllipticalGradientInfo using M_SQRT2 to make this + // gradient look 'nicer' this correction seems not 100% + // correct, but is close enough for now + if(fAspectRatio > 1.0) + { + t = 1.0 - std::hypot(aCoor.getX() / fAspectRatio, aCoor.getY()); + } + else if(fAspectRatio > 0.0) { - return floor(t * nSteps) / double(nSteps - 1); + t = 1.0 - std::hypot(aCoor.getX(), aCoor.getY() * fAspectRatio); } return t; } - double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) - { - return getRadialGradientAlpha(rUV, rGradInfo); // only matrix setup differs - } - double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) { const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); @@ -450,20 +684,71 @@ namespace basegfx return 0.0; } - const double t(1.0 - std::max(fAbsX, fAbsY)); - const sal_uInt32 nSteps(rGradInfo.getSteps()); + return 1.0 - std::max(fAbsX, fAbsY); + } + + double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) + { + const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV); + double fAbsX(fabs(aCoor.getX())); - if(nSteps && t < 1.0) + if(fAbsX >= 1.0) { - return floor(t * nSteps) / double(nSteps - 1); + return 0.0; } - return t; - } + double fAbsY(fabs(aCoor.getY())); - double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo) - { - return getSquareGradientAlpha(rUV, rGradInfo); // only matrix setup differs + if(fAbsY >= 1.0) + { + return 0.0; + } + + // MCGR: Visualizations using the texturing method for + // displaying gradients (getBackTextureTransform is + // involved) show wrong results for GradientElliptical + // and GradientRect, this can be best seen when using + // less steps, e.g. just four. This thus has influence + // on cppcanvas (slideshow) and 3D textures, so needs + // to be corrected. + // Missing is to use the aspect ratio of the object + // in this [-1, -1, 1, 1] unified coordinate space + // after getBackTextureTransform is applied. Optically + // in the larger direction of the texturing the color + // step distances are too big *because* we are in that + // unit range now. + // To correct that, a kind of 'limo stretching' needs to + // be applied, adding space around the center + // proportional to the aspect ratio, so the intuitive + // idea would be to do + // + // fAbsX' = ((fAspectRatio - 1) + fAbsX) / fAspectRatio + // + // which scales from the center. This does not work, and + // after some thoughts it's clear why: It's not the + // position that needs to be moved (this cannot be + // changed), but the position *before* that scale has + // to be determined to get the correct, shifted color + // for the already 'new' position. Thus, turn around + // the expression as + // + // fAbsX' * fAspectRatio = fAspectRatio - 1 + fAbsX + // fAbsX' * fAspectRatio - fAspectRatio + 1 = fAbsX + // fAbsX = (fAbsX' - 1) * fAspectRatio + 1 + // + // This works and can even be simply adapted for + // fAspectRatio < 1.0 aka vertical is bigger. + double fAspectRatio(rGradInfo.getAspectRatio()); + if(fAspectRatio > 1.0) + { + fAbsX = ((fAbsX - 1) * fAspectRatio) + 1; + } + else if(fAspectRatio > 0.0) + { + fAbsY = ((fAbsY - 1) / fAspectRatio) + 1; + } + + return 1.0 - std::max(fAbsX, fAbsY); } } // namespace utils } // namespace basegfx |