1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <basegfx/utils/gradienttools.hxx>
21 #include <basegfx/point/b2dpoint.hxx>
22 #include <basegfx/range/b2drange.hxx>
23 #include <basegfx/matrix/b2dhommatrixtools.hxx>
24 #include <com/sun/star/awt/Gradient2.hpp>
25 #include <osl/endian.h>
32 bool ODFGradientInfo::operator==(const ODFGradientInfo
& rODFGradientInfo
) const
34 return getTextureTransform() == rODFGradientInfo
.getTextureTransform()
35 && getAspectRatio() == rODFGradientInfo
.getAspectRatio()
36 && getRequestedSteps() == rODFGradientInfo
.getRequestedSteps();
39 const B2DHomMatrix
& ODFGradientInfo::getBackTextureTransform() const
41 if(maBackTextureTransform
.isIdentity())
43 const_cast< ODFGradientInfo
* >(this)->maBackTextureTransform
= getTextureTransform();
44 const_cast< ODFGradientInfo
* >(this)->maBackTextureTransform
.invert();
47 return maBackTextureTransform
;
50 /** Most of the setup for linear & axial gradient is the same, except
51 for the border treatment. Factored out here.
53 static ODFGradientInfo
init1DGradientInfo(
54 const B2DRange
& rTargetRange
,
60 B2DHomMatrix aTextureTransform
;
64 double fTargetSizeX(rTargetRange
.getWidth());
65 double fTargetSizeY(rTargetRange
.getHeight());
66 double fTargetOffsetX(rTargetRange
.getMinX());
67 double fTargetOffsetY(rTargetRange
.getMinY());
69 // add object expansion
70 const bool bAngleUsed(!fTools::equalZero(fAngle
));
74 const double fAbsCos(fabs(cos(fAngle
)));
75 const double fAbsSin(fabs(sin(fAngle
)));
76 const double fNewX(fTargetSizeX
* fAbsCos
+ fTargetSizeY
* fAbsSin
);
77 const double fNewY(fTargetSizeY
* fAbsCos
+ fTargetSizeX
* fAbsSin
);
79 fTargetOffsetX
-= (fNewX
- fTargetSizeX
) / 2.0;
80 fTargetOffsetY
-= (fNewY
- fTargetSizeY
) / 2.0;
85 const double fSizeWithoutBorder(1.0 - fBorder
);
89 aTextureTransform
.scale(1.0, fSizeWithoutBorder
* 0.5);
90 aTextureTransform
.translate(0.0, 0.5);
94 if(!fTools::equal(fSizeWithoutBorder
, 1.0))
96 aTextureTransform
.scale(1.0, fSizeWithoutBorder
);
97 aTextureTransform
.translate(0.0, fBorder
);
101 aTextureTransform
.scale(fTargetSizeX
, fTargetSizeY
);
103 // add texture rotate after scale to keep perpendicular angles
106 const B2DPoint
aCenter(0.5 * fTargetSizeX
, 0.5 * fTargetSizeY
);
108 aTextureTransform
*= basegfx::utils::createRotateAroundPoint(aCenter
, fAngle
);
111 // add object translate
112 aTextureTransform
.translate(fTargetOffsetX
, fTargetOffsetY
);
114 // prepare aspect for texture
115 const double fAspectRatio(fTools::equalZero(fTargetSizeY
) ? 1.0 : fTargetSizeX
/ fTargetSizeY
);
117 return ODFGradientInfo(aTextureTransform
, fAspectRatio
, nSteps
);
120 /** Most of the setup for radial & ellipsoidal gradient is the same,
121 except for the border treatment. Factored out here.
123 static ODFGradientInfo
initEllipticalGradientInfo(
124 const B2DRange
& rTargetRange
,
125 const B2DVector
& rOffset
,
131 B2DHomMatrix aTextureTransform
;
135 double fTargetSizeX(rTargetRange
.getWidth());
136 double fTargetSizeY(rTargetRange
.getHeight());
137 double fTargetOffsetX(rTargetRange
.getMinX());
138 double fTargetOffsetY(rTargetRange
.getMinY());
140 // add object expansion
143 const double fOriginalDiag(std::hypot(fTargetSizeX
, fTargetSizeY
));
145 fTargetOffsetX
-= (fOriginalDiag
- fTargetSizeX
) / 2.0;
146 fTargetOffsetY
-= (fOriginalDiag
- fTargetSizeY
) / 2.0;
147 fTargetSizeX
= fOriginalDiag
;
148 fTargetSizeY
= fOriginalDiag
;
152 fTargetOffsetX
-= ((M_SQRT2
- 1) / 2.0 ) * fTargetSizeX
;
153 fTargetOffsetY
-= ((M_SQRT2
- 1) / 2.0 ) * fTargetSizeY
;
154 fTargetSizeX
= M_SQRT2
* fTargetSizeX
;
155 fTargetSizeY
= M_SQRT2
* fTargetSizeY
;
158 const double fHalfBorder((1.0 - fBorder
) * 0.5);
160 aTextureTransform
.scale(fHalfBorder
, fHalfBorder
);
161 aTextureTransform
.translate(0.5, 0.5);
162 aTextureTransform
.scale(fTargetSizeX
, fTargetSizeY
);
164 // add texture rotate after scale to keep perpendicular angles
165 if(!bCircular
&& !fTools::equalZero(fAngle
))
167 const B2DPoint
aCenter(0.5 * fTargetSizeX
, 0.5 * fTargetSizeY
);
169 aTextureTransform
*= basegfx::utils::createRotateAroundPoint(aCenter
, fAngle
);
172 // add defined offsets after rotation
173 if(!fTools::equal(0.5, rOffset
.getX()) || !fTools::equal(0.5, rOffset
.getY()))
175 // use original target size
176 fTargetOffsetX
+= (rOffset
.getX() - 0.5) * rTargetRange
.getWidth();
177 fTargetOffsetY
+= (rOffset
.getY() - 0.5) * rTargetRange
.getHeight();
180 // add object translate
181 aTextureTransform
.translate(fTargetOffsetX
, fTargetOffsetY
);
183 // prepare aspect for texture
184 const double fAspectRatio(fTargetSizeY
== 0.0 ? 1.0 : (fTargetSizeX
/ fTargetSizeY
));
186 return ODFGradientInfo(aTextureTransform
, fAspectRatio
, nSteps
);
189 /** Setup for rect & square gradient is exactly the same. Factored out
192 static ODFGradientInfo
initRectGradientInfo(
193 const B2DRange
& rTargetRange
,
194 const B2DVector
& rOffset
,
200 B2DHomMatrix aTextureTransform
;
204 double fTargetSizeX(rTargetRange
.getWidth());
205 double fTargetSizeY(rTargetRange
.getHeight());
206 double fTargetOffsetX(rTargetRange
.getMinX());
207 double fTargetOffsetY(rTargetRange
.getMinY());
209 // add object expansion
212 const double fSquareWidth(std::max(fTargetSizeX
, fTargetSizeY
));
214 fTargetOffsetX
-= (fSquareWidth
- fTargetSizeX
) / 2.0;
215 fTargetOffsetY
-= (fSquareWidth
- fTargetSizeY
) / 2.0;
216 fTargetSizeX
= fTargetSizeY
= fSquareWidth
;
219 // add object expansion
220 const bool bAngleUsed(!fTools::equalZero(fAngle
));
224 const double fAbsCos(fabs(cos(fAngle
)));
225 const double fAbsSin(fabs(sin(fAngle
)));
226 const double fNewX(fTargetSizeX
* fAbsCos
+ fTargetSizeY
* fAbsSin
);
227 const double fNewY(fTargetSizeY
* fAbsCos
+ fTargetSizeX
* fAbsSin
);
229 fTargetOffsetX
-= (fNewX
- fTargetSizeX
) / 2.0;
230 fTargetOffsetY
-= (fNewY
- fTargetSizeY
) / 2.0;
231 fTargetSizeX
= fNewX
;
232 fTargetSizeY
= fNewY
;
235 const double fHalfBorder((1.0 - fBorder
) * 0.5);
237 aTextureTransform
.scale(fHalfBorder
, fHalfBorder
);
238 aTextureTransform
.translate(0.5, 0.5);
239 aTextureTransform
.scale(fTargetSizeX
, fTargetSizeY
);
241 // add texture rotate after scale to keep perpendicular angles
244 const B2DPoint
aCenter(0.5 * fTargetSizeX
, 0.5 * fTargetSizeY
);
246 aTextureTransform
*= basegfx::utils::createRotateAroundPoint(aCenter
, fAngle
);
249 // add defined offsets after rotation
250 if(!fTools::equal(0.5, rOffset
.getX()) || !fTools::equal(0.5, rOffset
.getY()))
252 // use original target size
253 fTargetOffsetX
+= (rOffset
.getX() - 0.5) * rTargetRange
.getWidth();
254 fTargetOffsetY
+= (rOffset
.getY() - 0.5) * rTargetRange
.getHeight();
257 // add object translate
258 aTextureTransform
.translate(fTargetOffsetX
, fTargetOffsetY
);
260 // prepare aspect for texture
261 const double fAspectRatio(fTargetSizeY
== 0.0 ? 1.0 : (fTargetSizeX
/ fTargetSizeY
));
263 return ODFGradientInfo(aTextureTransform
, fAspectRatio
, nSteps
);
268 /* Tooling method to extract data from given BGradient
269 to ColorStops, doing some corrections, partially based
270 on given SingleColor */
271 void prepareColorStops(
272 const basegfx::BGradient
& rGradient
,
273 BColorStops
& rColorStops
,
274 BColor
& rSingleColor
)
276 if (rGradient
.GetColorStops().isSingleColor(rSingleColor
))
278 // when single color, preserve value in rSingleColor
279 // and clear the ColorStops, done.
284 const bool bAdaptStartEndIntensity(100 != rGradient
.GetStartIntens() || 100 != rGradient
.GetEndIntens());
285 const bool bAdaptBorder(0 != rGradient
.GetBorder());
287 if (!bAdaptStartEndIntensity
&& !bAdaptBorder
)
289 // copy unchanged ColorStops & done
290 rColorStops
= rGradient
.GetColorStops();
294 // prepare a copy to work on
295 basegfx::BGradient
aWorkCopy(rGradient
);
297 if (bAdaptStartEndIntensity
)
299 aWorkCopy
.tryToApplyStartEndIntensity();
301 // this can again lead to single color (e.g. both zero, so
302 // all black), so check again for it
303 if (aWorkCopy
.GetColorStops().isSingleColor(rSingleColor
))
312 aWorkCopy
.tryToApplyBorder();
315 // extract ColorStops, that's all we need here
316 rColorStops
= aWorkCopy
.GetColorStops();
319 /* Tooling method to synchronize the given ColorStops.
320 The intention is that a color GradientStops and an
321 alpha/transparence GradientStops gets synchronized
323 void synchronizeColorStops(
324 BColorStops
& rColorStops
,
325 BColorStops
& rAlphaStops
,
326 const BColor
& rSingleColor
,
327 const BColor
& rSingleAlpha
)
329 if (rColorStops
.empty())
331 if (rAlphaStops
.empty())
333 // no AlphaStops and no ColorStops
334 // create two-stop fallbacks for both
335 rColorStops
= BColorStops
{
336 BColorStop(0.0, rSingleColor
),
337 BColorStop(1.0, rSingleColor
) };
338 rAlphaStops
= BColorStops
{
339 BColorStop(0.0, rSingleAlpha
),
340 BColorStop(1.0, rSingleAlpha
) };
344 // AlphaStops but no ColorStops
345 // create fallback synched with existing AlphaStops
346 for (const auto& cand
: rAlphaStops
)
348 rColorStops
.emplace_back(cand
.getStopOffset(), rSingleColor
);
352 // preparations complete, we are done
355 else if (rAlphaStops
.empty())
357 // ColorStops but no AlphaStops
358 // create fallback AlphaStops synched with existing ColorStops using SingleAlpha
359 for (const auto& cand
: rColorStops
)
361 rAlphaStops
.emplace_back(cand
.getStopOffset(), rSingleAlpha
);
364 // preparations complete, we are done
368 // here we have ColorStops and AlphaStops not empty. Check if we need to
369 // synchronize both or if they are already usable/in a synched state so
370 // that they have same count and same StopOffsets
371 bool bNeedToSyncronize(rColorStops
.size() != rAlphaStops
.size());
373 if (!bNeedToSyncronize
)
375 // check for same StopOffsets
376 BColorStops::const_iterator
aCurrColor(rColorStops
.begin());
377 BColorStops::const_iterator
aCurrAlpha(rAlphaStops
.begin());
379 while (!bNeedToSyncronize
&&
380 aCurrColor
!= rColorStops
.end() &&
381 aCurrAlpha
!= rAlphaStops
.end())
383 if (fTools::equal(aCurrColor
->getStopOffset(), aCurrAlpha
->getStopOffset()))
390 bNeedToSyncronize
= true;
395 if (bNeedToSyncronize
)
397 // synchronize sizes & StopOffsets
398 BColorStops::const_iterator
aCurrColor(rColorStops
.begin());
399 BColorStops::const_iterator
aCurrAlpha(rAlphaStops
.begin());
400 BColorStops aNewColor
;
401 BColorStops aNewAlpha
;
402 BColorStops::BColorStopRange aColorStopRange
;
403 BColorStops::BColorStopRange aAlphaStopRange
;
404 bool bRealChange(false);
407 const bool bColor(aCurrColor
!= rColorStops
.end());
408 const bool bAlpha(aCurrAlpha
!= rAlphaStops
.end());
410 if (bColor
&& bAlpha
)
412 const double fColorOff(aCurrColor
->getStopOffset());
413 const double fAlphaOff(aCurrAlpha
->getStopOffset());
415 if (fTools::less(fColorOff
, fAlphaOff
))
417 // copy color, create alpha
418 aNewColor
.emplace_back(fColorOff
, aCurrColor
->getStopColor());
419 aNewAlpha
.emplace_back(fColorOff
, rAlphaStops
.getInterpolatedBColor(fColorOff
, 0, aAlphaStopRange
));
423 else if (fTools::more(fColorOff
, fAlphaOff
))
425 // copy alpha, create color
426 aNewColor
.emplace_back(fAlphaOff
, rColorStops
.getInterpolatedBColor(fAlphaOff
, 0, aColorStopRange
));
427 aNewAlpha
.emplace_back(fAlphaOff
, aCurrAlpha
->getStopColor());
433 // equal: copy both, advance
434 aNewColor
.emplace_back(fColorOff
, aCurrColor
->getStopColor());
435 aNewAlpha
.emplace_back(fAlphaOff
, aCurrAlpha
->getStopColor());
442 const double fColorOff(aCurrColor
->getStopOffset());
443 aNewAlpha
.emplace_back(fColorOff
, rAlphaStops
.getInterpolatedBColor(fColorOff
, 0, aAlphaStopRange
));
444 aNewColor
.emplace_back(fColorOff
, aCurrColor
->getStopColor());
450 const double fAlphaOff(aCurrAlpha
->getStopOffset());
451 aNewColor
.emplace_back(fAlphaOff
, rColorStops
.getInterpolatedBColor(fAlphaOff
, 0, aColorStopRange
));
452 aNewAlpha
.emplace_back(fAlphaOff
, aCurrAlpha
->getStopColor());
458 // no more input, break do..while loop
466 // copy on 'real' change, that means data was added.
467 // This should always be the cease and should have been
468 // detected as such above, see bNeedToSyncronize
469 rColorStops
= aNewColor
;
470 rAlphaStops
= aNewAlpha
; // MCGR: tdf#155537 used wrong result here
475 sal_uInt32
calculateNumberOfSteps(
476 sal_uInt32 nRequestedSteps
,
477 const BColor
& rStart
,
480 const sal_uInt32
nMaxSteps(sal_uInt32((rStart
.getMaximumDistance(rEnd
) * 127.5) + 0.5));
482 if (0 == nRequestedSteps
)
484 nRequestedSteps
= nMaxSteps
;
487 if(nRequestedSteps
> nMaxSteps
)
489 nRequestedSteps
= nMaxSteps
;
492 return std::max(sal_uInt32(1), nRequestedSteps
);
495 ODFGradientInfo
createLinearODFGradientInfo(
496 const B2DRange
& rTargetArea
,
501 return init1DGradientInfo(
509 ODFGradientInfo
createAxialODFGradientInfo(
510 const B2DRange
& rTargetArea
,
515 return init1DGradientInfo(
523 ODFGradientInfo
createRadialODFGradientInfo(
524 const B2DRange
& rTargetArea
,
525 const B2DVector
& rOffset
,
529 return initEllipticalGradientInfo(
538 ODFGradientInfo
createEllipticalODFGradientInfo(
539 const B2DRange
& rTargetArea
,
540 const B2DVector
& rOffset
,
545 return initEllipticalGradientInfo(
554 ODFGradientInfo
createSquareODFGradientInfo(
555 const B2DRange
& rTargetArea
,
556 const B2DVector
& rOffset
,
561 return initRectGradientInfo(
570 ODFGradientInfo
createRectangularODFGradientInfo(
571 const B2DRange
& rTargetArea
,
572 const B2DVector
& rOffset
,
577 return initRectGradientInfo(
586 double getLinearGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
588 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
590 // Ignore X, this is not needed at all for Y-Oriented gradients
591 // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
596 if(aCoor
.getY() <= 0.0)
598 return 0.0; // start value for inside
601 if(aCoor
.getY() >= 1.0)
603 return 1.0; // end value for outside
609 double getAxialGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
611 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
613 // Ignore X, this is not needed at all for Y-Oriented gradients
614 //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
619 const double fAbsY(fabs(aCoor
.getY()));
623 return 1.0; // use end value when outside in Y
629 double getRadialGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
631 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
633 if(aCoor
.getX() < -1.0 || aCoor
.getX() > 1.0 || aCoor
.getY() < -1.0 || aCoor
.getY() > 1.0)
638 return 1.0 - std::hypot(aCoor
.getX(), aCoor
.getY());
641 double getEllipticalGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
643 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
645 if(aCoor
.getX() < -1.0 || aCoor
.getX() > 1.0 || aCoor
.getY() < -1.0 || aCoor
.getY() > 1.0)
650 double fAspectRatio(rGradInfo
.getAspectRatio());
653 // MCGR: Similar to getRectangularGradientAlpha (please
654 // see there) we need to use aspect ratio here. Due to
655 // initEllipticalGradientInfo using M_SQRT2 to make this
656 // gradient look 'nicer' this correction seems not 100%
657 // correct, but is close enough for now
658 if(fAspectRatio
> 1.0)
660 t
= 1.0 - std::hypot(aCoor
.getX() / fAspectRatio
, aCoor
.getY());
662 else if(fAspectRatio
> 0.0)
664 t
= 1.0 - std::hypot(aCoor
.getX(), aCoor
.getY() * fAspectRatio
);
670 double getSquareGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
672 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
673 const double fAbsX(fabs(aCoor
.getX()));
680 const double fAbsY(fabs(aCoor
.getY()));
687 return 1.0 - std::max(fAbsX
, fAbsY
);
690 double getRectangularGradientAlpha(const B2DPoint
& rUV
, const ODFGradientInfo
& rGradInfo
)
692 const B2DPoint
aCoor(rGradInfo
.getBackTextureTransform() * rUV
);
693 double fAbsX(fabs(aCoor
.getX()));
700 double fAbsY(fabs(aCoor
.getY()));
707 // MCGR: Visualizations using the texturing method for
708 // displaying gradients (getBackTextureTransform is
709 // involved) show wrong results for GradientElliptical
710 // and GradientRect, this can be best seen when using
711 // less steps, e.g. just four. This thus has influence
712 // on cppcanvas (slideshow) and 3D textures, so needs
714 // Missing is to use the aspect ratio of the object
715 // in this [-1, -1, 1, 1] unified coordinate space
716 // after getBackTextureTransform is applied. Optically
717 // in the larger direction of the texturing the color
718 // step distances are too big *because* we are in that
720 // To correct that, a kind of 'limo stretching' needs to
721 // be applied, adding space around the center
722 // proportional to the aspect ratio, so the intuitive
723 // idea would be to do
725 // fAbsX' = ((fAspectRatio - 1) + fAbsX) / fAspectRatio
727 // which scales from the center. This does not work, and
728 // after some thoughts it's clear why: It's not the
729 // position that needs to be moved (this cannot be
730 // changed), but the position *before* that scale has
731 // to be determined to get the correct, shifted color
732 // for the already 'new' position. Thus, turn around
735 // fAbsX' * fAspectRatio = fAspectRatio - 1 + fAbsX
736 // fAbsX' * fAspectRatio - fAspectRatio + 1 = fAbsX
737 // fAbsX = (fAbsX' - 1) * fAspectRatio + 1
739 // This works and can even be simply adapted for
740 // fAspectRatio < 1.0 aka vertical is bigger.
741 double fAspectRatio(rGradInfo
.getAspectRatio());
742 if(fAspectRatio
> 1.0)
744 fAbsX
= ((fAbsX
- 1) * fAspectRatio
) + 1;
746 else if(fAspectRatio
> 0.0)
748 fAbsY
= ((fAbsY
- 1) / fAspectRatio
) + 1;
751 return 1.0 - std::max(fAbsX
, fAbsY
);
754 } // namespace basegfx
756 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */