summaryrefslogtreecommitdiffstats
path: root/basegfx/source/tools/gradienttools.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /basegfx/source/tools/gradienttools.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'basegfx/source/tools/gradienttools.cxx')
-rw-r--r--basegfx/source/tools/gradienttools.cxx756
1 files changed, 756 insertions, 0 deletions
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx
new file mode 100644
index 0000000000..8f3e8ae83c
--- /dev/null
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -0,0 +1,756 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <basegfx/utils/gradienttools.hxx>
+#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
+{
+ bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
+ {
+ return getTextureTransform() == rODFGradientInfo.getTextureTransform()
+ && getAspectRatio() == rODFGradientInfo.getAspectRatio()
+ && getRequestedSteps() == rODFGradientInfo.getRequestedSteps();
+ }
+
+ const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const
+ {
+ if(maBackTextureTransform.isIdentity())
+ {
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform = getTextureTransform();
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform.invert();
+ }
+
+ return maBackTextureTransform;
+ }
+
+ /** Most of the setup for linear & axial gradient is the same, except
+ for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo init1DGradientInfo(
+ const B2DRange& rTargetRange,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bAxial)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fSizeWithoutBorder(1.0 - fBorder);
+
+ if(bAxial)
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder * 0.5);
+ aTextureTransform.translate(0.0, 0.5);
+ }
+ else
+ {
+ if(!fTools::equal(fSizeWithoutBorder, 1.0))
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder);
+ aTextureTransform.translate(0.0, fBorder);
+ }
+ }
+
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTools::equalZero(fTargetSizeY) ? 1.0 : fTargetSizeX / fTargetSizeY);
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Most of the setup for radial & ellipsoidal gradient is the same,
+ except for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo initEllipticalGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bCircular)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bCircular)
+ {
+ const double fOriginalDiag(std::hypot(fTargetSizeX, fTargetSizeY));
+
+ fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0;
+ fTargetSizeX = fOriginalDiag;
+ fTargetSizeY = fOriginalDiag;
+ }
+ else
+ {
+ 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);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(!bCircular && !fTools::equalZero(fAngle))
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use original target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
+ fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Setup for rect & square gradient is exactly the same. Factored out
+ here.
+ */
+ static ODFGradientInfo initRectGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bSquare)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bSquare)
+ {
+ const double fSquareWidth(std::max(fTargetSizeX, fTargetSizeY));
+
+ fTargetOffsetX -= (fSquareWidth - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fSquareWidth - fTargetSizeY) / 2.0;
+ fTargetSizeX = fTargetSizeY = fSquareWidth;
+ }
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fHalfBorder((1.0 - fBorder) * 0.5);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use original target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
+ fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ 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,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createAxialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRadialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ 0.0,
+ true);
+ }
+
+ ODFGradientInfo createEllipticalODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createSquareODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRectangularODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore X, this is not needed at all for Y-Oriented gradients
+ // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ // {
+ // return 0.0;
+ // }
+
+ if(aCoor.getY() <= 0.0)
+ {
+ return 0.0; // start value for inside
+ }
+
+ if(aCoor.getY() >= 1.0)
+ {
+ return 1.0; // end value for outside
+ }
+
+ return aCoor.getY();
+ }
+
+ double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore X, this is not needed at all for Y-Oriented gradients
+ //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ //{
+ // return 0.0;
+ //}
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 1.0; // use end value when outside in Y
+ }
+
+ return fAbsY;
+ }
+
+ 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 0.0;
+ }
+
+ return 1.0 - std::hypot(aCoor.getX(), aCoor.getY());
+ }
+
+ double getEllipticalGradientAlpha(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 0.0;
+ }
+
+ double fAspectRatio(rGradInfo.getAspectRatio());
+ double 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)
+ {
+ t = 1.0 - std::hypot(aCoor.getX(), aCoor.getY() * fAspectRatio);
+ }
+
+ return t;
+ }
+
+ double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+ const double fAbsX(fabs(aCoor.getX()));
+
+ if(fAbsX >= 1.0)
+ {
+ return 0.0;
+ }
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 0.0;
+ }
+
+ 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(fAbsX >= 1.0)
+ {
+ return 0.0;
+ }
+
+ double fAbsY(fabs(aCoor.getY()));
+
+ 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
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */