forked from amazingfate/loongoffice
While synchronizing color and transparency gradients two new sequences are generated. In case the last offsets where different, the remaining stops where not copied to the new sequence and thus they had different sizes which triggered an assert in oox/source/export/drawingml.cxx. Change-Id: I446f8cfafb23735f06ad4e05eee8c922141b864d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153063 Tested-by: Jenkins Tested-by: Caolán McNamara <caolan.mcnamara@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
757 lines
26 KiB
C++
757 lines
26 KiB
C++
/* -*- 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: */
|