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 <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
21 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
22 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
23 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
25 #include <basegfx/polygon/b2dpolygontools.hxx>
26 #include <basegfx/polygon/b2dpolygon.hxx>
27 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
31 #include <drawinglayer/geometry/viewinformation2d.hxx>
32 #include <osl/diagnose.h>
33 #include <sal/log.hxx>
36 #include <vcl/skia/SkiaHelper.hxx>
38 using namespace com::sun::star
;
43 sal_uInt32
calculateStepsForSvgGradient(const basegfx::BColor
& rColorA
, const basegfx::BColor
& rColorB
, double fDelta
, double fDiscreteUnit
)
45 // use color distance, assume to do every color step (full quality)
46 sal_uInt32
nSteps(basegfx::fround(rColorA
.getDistance(rColorB
) * 255.0));
50 // calc discrete length to change color all 1.5 discrete units (pixels)
51 const sal_uInt32
nDistSteps(basegfx::fround(fDelta
/ (fDiscreteUnit
* 1.5)));
53 nSteps
= std::min(nSteps
, nDistSteps
);
56 // roughly cut when too big or too small
57 nSteps
= std::min(nSteps
, sal_uInt32(255));
58 nSteps
= std::max(nSteps
, sal_uInt32(1));
62 } // end of anonymous namespace
65 namespace drawinglayer::primitive2d
67 Primitive2DReference
SvgGradientHelper::createSingleGradientEntryFill() const
69 const SvgGradientEntryVector
& rEntries(getGradientEntries());
70 const sal_uInt32
nCount(rEntries
.size());
75 OSL_ENSURE(false, "Single gradient entry construction without entry (!)");
79 const SvgGradientEntry
& rSingleEntry(rEntries
[nCount
- 1]);
80 const double fOpacity(rSingleEntry
.getOpacity());
82 if (fOpacity
<= 0.0 || basegfx::fTools::equalZero(fOpacity
))
84 // completely opaque, done
88 if (basegfx::fTools::moreOrEqual(fOpacity
, 1.0))
91 return Primitive2DReference
{
92 new PolyPolygonColorPrimitive2D(
94 rSingleEntry
.getColor()) };
97 // if transparent, use PolyPolygonRGBAPrimitive2D
98 return Primitive2DReference
{
99 new PolyPolygonRGBAPrimitive2D(
101 rSingleEntry
.getColor(),
105 void SvgGradientHelper::checkPreconditions()
107 const SvgGradientEntryVector
& rEntries
= getGradientEntries();
111 // no fill at all, done
115 // sort maGradientEntries by offset, small to big
116 std::sort(maGradientEntries
.begin(), maGradientEntries
.end());
118 // gradient with at least two colors
119 bool bAllInvisible(true);
120 bool bInvalidEntries(false);
122 for(const SvgGradientEntry
& rCandidate
: rEntries
)
124 if(basegfx::fTools::equalZero(rCandidate
.getOpacity()))
127 mbFullyOpaque
= false;
129 else if(basegfx::fTools::equal(rCandidate
.getOpacity(), 1.0))
132 bAllInvisible
= false;
137 bAllInvisible
= false;
138 mbFullyOpaque
= false;
141 if(!basegfx::fTools::betweenOrEqualEither(rCandidate
.getOffset(), 0.0, 1.0))
143 bInvalidEntries
= true;
149 // all invisible, nothing to do
155 // invalid entries, do nothing
156 SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]");
160 const basegfx::B2DRange
aPolyRange(getPolyPolygon().getB2DRange());
162 if(aPolyRange
.isEmpty())
164 // no range to fill, nothing to do
168 const double fPolyWidth(aPolyRange
.getWidth());
169 const double fPolyHeight(aPolyRange
.getHeight());
171 if(basegfx::fTools::equalZero(fPolyWidth
) || basegfx::fTools::equalZero(fPolyHeight
))
173 // no width/height to fill, nothing to do
177 mbCreatesContent
= true;
179 if(1 == rEntries
.size())
181 // fill with single existing color
186 const SvgGradientEntry
& SvgGradientHelper::FindEntryLessOrEqual(
188 const double fFrac
) const
190 const bool bMirror(SpreadMethod::Reflect
== getSpreadMethod() && 0 != rInt
% 2);
191 const SvgGradientEntryVector
& rCurrent(bMirror
? getMirroredGradientEntries() : getGradientEntries());
193 for(SvgGradientEntryVector::const_reverse_iterator
aIter(rCurrent
.rbegin()); aIter
!= rCurrent
.rend(); ++aIter
)
195 if(basegfx::fTools::lessOrEqual(aIter
->getOffset(), fFrac
))
201 // walk over gap to the left, be prepared for missing 0.0/1.0 entries
203 const bool bMirror2(SpreadMethod::Reflect
== getSpreadMethod() && 0 != rInt
% 2);
204 const SvgGradientEntryVector
& rCurrent2(bMirror2
? getMirroredGradientEntries() : getGradientEntries());
205 return rCurrent2
.back();
208 const SvgGradientEntry
& SvgGradientHelper::FindEntryMore(
210 const double fFrac
) const
212 const bool bMirror(SpreadMethod::Reflect
== getSpreadMethod() && 0 != rInt
% 2);
213 const SvgGradientEntryVector
& rCurrent(bMirror
? getMirroredGradientEntries() : getGradientEntries());
215 for(SvgGradientEntryVector::const_iterator
aIter(rCurrent
.begin()); aIter
!= rCurrent
.end(); ++aIter
)
217 if(basegfx::fTools::more(aIter
->getOffset(), fFrac
))
223 // walk over gap to the right, be prepared for missing 0.0/1.0 entries
225 const bool bMirror2(SpreadMethod::Reflect
== getSpreadMethod() && 0 != rInt
% 2);
226 const SvgGradientEntryVector
& rCurrent2(bMirror2
? getMirroredGradientEntries() : getGradientEntries());
227 return rCurrent2
.front();
230 // tdf#124424 Adapted creation of color runs to do in a single effort. Previous
231 // version tried to do this from [0.0 .. 1.0] and to re-use transformed versions
232 // in the caller if SpreadMethod was on some repeat mode, but had problems when
233 // e.g. like in the bugdoc from the task a negative-only fStart/fEnd run was
234 // requested in which case it did nothing. Even when reusing the spread might
235 // not have been a full one from [0.0 .. 1.0].
236 // This gets complicated due to mirrored runs, but also for gradient definitions
237 // with missing entries for 0.0 and 1.0 in which case these have to be guessed
238 // to be there with same parametrisation as their nearest existing entries. These
239 // *could* have been added at checkPreconditions() but would then create unnecessary
240 // spreads on zone overlaps.
241 void SvgGradientHelper::createRun(
242 Primitive2DContainer
& rTargetColor
,
243 Primitive2DContainer
& rTargetOpacity
,
251 if(SpreadMethod::Pad
== getSpreadMethod())
255 fFrac
= std::modf(fStart
, &fInt
);
256 const SvgGradientEntry
& rFront(getGradientEntries().front());
257 const SvgGradientEntry
aTemp(1.0 + fFrac
, rFront
.getColor(), rFront
.getOpacity());
258 createAtom(rTargetColor
, rTargetOpacity
, aTemp
, rFront
, static_cast<sal_Int32
>(fInt
- 1), 0);
259 fStart
= rFront
.getOffset();
264 // change fEnd early, but create geometry later (after range below)
266 fEnd
= getGradientEntries().back().getOffset();
272 fFrac
= std::modf(fStart
, &fInt
);
280 sal_Int32
nIntLeft(static_cast<sal_Int32
>(fInt
));
281 sal_Int32
nIntRight(nIntLeft
);
283 const SvgGradientEntry
& rLeft(FindEntryLessOrEqual(nIntLeft
, fFrac
));
284 const SvgGradientEntry
& rRight(FindEntryMore(nIntRight
, fFrac
));
285 createAtom(rTargetColor
, rTargetOpacity
, rLeft
, rRight
, nIntLeft
, nIntRight
);
287 const double fNextfStart(static_cast<double>(nIntRight
) + rRight
.getOffset());
289 if(basegfx::fTools::more(fNextfStart
, fStart
))
291 fStart
= fNextfStart
;
295 SAL_WARN("drawinglayer", "SvgGradientHelper spread error");
302 // create end run for SpreadMethod::Pad late to keep correct creation order
303 fFrac
= std::modf(fEnd2
, &fInt
);
304 const SvgGradientEntry
& rBack(getGradientEntries().back());
305 const SvgGradientEntry
aTemp(fFrac
, rBack
.getColor(), rBack
.getOpacity());
306 createAtom(rTargetColor
, rTargetOpacity
, rBack
, aTemp
, 0, static_cast<sal_Int32
>(fInt
));
310 Primitive2DReference
SvgGradientHelper::createResult(
311 Primitive2DContainer aTargetColor
,
312 Primitive2DContainer aTargetOpacity
,
313 const basegfx::B2DHomMatrix
& rUnitGradientToObject
,
316 Primitive2DContainer
aTargetColorEntries(aTargetColor
.maybeInvert(bInvert
));
317 Primitive2DContainer
aTargetOpacityEntries(aTargetOpacity
.maybeInvert(bInvert
));
319 if(aTargetColorEntries
.empty())
322 Primitive2DReference xRefContent
;
324 if(!aTargetOpacityEntries
.empty())
326 const Primitive2DReference xRefOpacity
= new TransparencePrimitive2D(
327 std::move(aTargetColorEntries
),
328 std::move(aTargetOpacityEntries
));
330 xRefContent
= new TransformPrimitive2D(
331 rUnitGradientToObject
,
332 Primitive2DContainer
{ xRefOpacity
});
336 xRefContent
= new TransformPrimitive2D(
337 rUnitGradientToObject
,
338 std::move(aTargetColorEntries
));
341 return new MaskPrimitive2D(
343 Primitive2DContainer
{ xRefContent
});
346 SvgGradientHelper::SvgGradientHelper(
347 basegfx::B2DHomMatrix aGradientTransform
,
348 basegfx::B2DPolyPolygon aPolyPolygon
,
349 SvgGradientEntryVector
&& rGradientEntries
,
350 const basegfx::B2DPoint
& rStart
,
351 bool bUseUnitCoordinates
,
352 SpreadMethod aSpreadMethod
)
353 : maGradientTransform(std::move(aGradientTransform
)),
354 maPolyPolygon(std::move(aPolyPolygon
)),
355 maGradientEntries(std::move(rGradientEntries
)),
357 maSpreadMethod(aSpreadMethod
),
358 mbCreatesContent(false),
359 mbSingleEntry(false),
361 mbUseUnitCoordinates(bUseUnitCoordinates
)
365 SvgGradientHelper::~SvgGradientHelper()
369 const SvgGradientEntryVector
& SvgGradientHelper::getMirroredGradientEntries() const
371 if(maMirroredGradientEntries
.empty() && !getGradientEntries().empty())
373 const_cast< SvgGradientHelper
* >(this)->createMirroredGradientEntries();
376 return maMirroredGradientEntries
;
379 void SvgGradientHelper::createMirroredGradientEntries()
381 if(!maMirroredGradientEntries
.empty() || getGradientEntries().empty())
384 const sal_uInt32
nCount(getGradientEntries().size());
385 maMirroredGradientEntries
.clear();
386 maMirroredGradientEntries
.reserve(nCount
);
388 for(sal_uInt32
a(0); a
< nCount
; a
++)
390 const SvgGradientEntry
& rCandidate
= getGradientEntries()[nCount
- 1 - a
];
392 maMirroredGradientEntries
.emplace_back(
393 1.0 - rCandidate
.getOffset(),
394 rCandidate
.getColor(),
395 rCandidate
.getOpacity());
399 bool SvgGradientHelper::operator==(const SvgGradientHelper
& rSvgGradientHelper
) const
401 const SvgGradientHelper
& rCompare
= rSvgGradientHelper
;
403 return (getGradientTransform() == rCompare
.getGradientTransform()
404 && getPolyPolygon() == rCompare
.getPolyPolygon()
405 && getGradientEntries() == rCompare
.getGradientEntries()
406 && getStart() == rCompare
.getStart()
407 && getUseUnitCoordinates() == rCompare
.getUseUnitCoordinates()
408 && getSpreadMethod() == rCompare
.getSpreadMethod());
411 } // end of namespace drawinglayer::primitive2d
414 namespace drawinglayer::primitive2d
416 void SvgLinearGradientPrimitive2D::checkPreconditions()
419 SvgGradientHelper::checkPreconditions();
421 if(getCreatesContent())
424 const basegfx::B2DVector
aVector(getEnd() - getStart());
426 if(basegfx::fTools::equalZero(aVector
.getX()) && basegfx::fTools::equalZero(aVector
.getY()))
428 // fill with single color using last stop color
434 void SvgLinearGradientPrimitive2D::createAtom(
435 Primitive2DContainer
& rTargetColor
,
436 Primitive2DContainer
& rTargetOpacity
,
437 const SvgGradientEntry
& rFrom
,
438 const SvgGradientEntry
& rTo
,
439 sal_Int32 nOffsetFrom
,
440 sal_Int32 nOffsetTo
) const
442 // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
443 if(rFrom
.getOffset() == rTo
.getOffset())
445 OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
449 rTargetColor
.push_back(
450 new SvgLinearAtomPrimitive2D(
451 rFrom
.getColor(), rFrom
.getOffset() + nOffsetFrom
,
452 rTo
.getColor(), rTo
.getOffset() + nOffsetTo
));
454 if(!getFullyOpaque())
456 const double fTransFrom(1.0 - rFrom
.getOpacity());
457 const double fTransTo(1.0 - rTo
.getOpacity());
458 const basegfx::BColor
aColorFrom(fTransFrom
, fTransFrom
, fTransFrom
);
459 const basegfx::BColor
aColorTo(fTransTo
, fTransTo
, fTransTo
);
461 rTargetOpacity
.push_back(
462 new SvgLinearAtomPrimitive2D(
463 aColorFrom
, rFrom
.getOffset() + nOffsetFrom
,
464 aColorTo
, rTo
.getOffset() + nOffsetTo
));
469 basegfx::B2DHomMatrix
SvgLinearGradientPrimitive2D::createUnitGradientToObjectTransformation() const
471 const basegfx::B2DRange
aPolyRange(getPolyPolygon().getB2DRange());
472 const double fPolyWidth(aPolyRange
.getWidth());
473 const double fPolyHeight(aPolyRange
.getHeight());
475 // create ObjectTransform based on polygon range
476 const basegfx::B2DHomMatrix
aObjectTransform(
477 basegfx::utils::createScaleTranslateB2DHomMatrix(
478 fPolyWidth
, fPolyHeight
,
479 aPolyRange
.getMinX(), aPolyRange
.getMinY()));
480 basegfx::B2DHomMatrix aUnitGradientToObject
;
482 if(getUseUnitCoordinates())
484 // interpret in unit coordinate system -> object aspect ratio will scale result
485 // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given
486 // gradient vector defined by Start,End
487 const basegfx::B2DVector
aVector(getEnd() - getStart());
488 const double fVectorLength(aVector
.getLength());
490 aUnitGradientToObject
.scale(fVectorLength
, 1.0);
491 aUnitGradientToObject
.rotate(atan2(aVector
.getY(), aVector
.getX()));
492 aUnitGradientToObject
.translate(getStart().getX(), getStart().getY());
494 aUnitGradientToObject
*= getGradientTransform();
496 // create full transform from unit gradient coordinates to object coordinates
497 // including the SvgGradient transformation
498 aUnitGradientToObject
*= aObjectTransform
;
502 // interpret in object coordinate system -> object aspect ratio will not scale result
503 const basegfx::B2DPoint
aStart(aObjectTransform
* getStart());
504 const basegfx::B2DPoint
aEnd(aObjectTransform
* getEnd());
505 const basegfx::B2DVector
aVector(aEnd
- aStart
);
507 aUnitGradientToObject
.scale(aVector
.getLength(), 1.0);
508 aUnitGradientToObject
.rotate(atan2(aVector
.getY(), aVector
.getX()));
509 aUnitGradientToObject
.translate(aStart
.getX(), aStart
.getY());
511 aUnitGradientToObject
*= getGradientTransform();
514 return aUnitGradientToObject
;
517 Primitive2DReference
SvgLinearGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D
& /*rViewInformation*/) const
521 // fill with last existing color
522 return createSingleGradientEntryFill();
524 else if(getCreatesContent())
526 // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
527 // invisible, width and height to fill are not empty
528 basegfx::B2DHomMatrix
aUnitGradientToObject(createUnitGradientToObjectTransformation());
530 // create inverse from it
531 basegfx::B2DHomMatrix
aObjectToUnitGradient(aUnitGradientToObject
);
532 aObjectToUnitGradient
.invert();
534 // back-transform polygon to unit gradient coordinates and get
535 // UnitRage. This is the range the gradient has to cover
536 basegfx::B2DPolyPolygon
aUnitPoly(getPolyPolygon());
537 aUnitPoly
.transform(aObjectToUnitGradient
);
538 const basegfx::B2DRange
aUnitRange(aUnitPoly
.getB2DRange());
540 // prepare result vectors
541 Primitive2DContainer aTargetColor
;
542 Primitive2DContainer aTargetOpacity
;
544 if(aUnitRange
.getWidth() > 0.0)
546 // add a pre-multiply to aUnitGradientToObject to allow
547 // multiplication of the polygon(xl, 0.0, xr, 1.0)
548 const basegfx::B2DHomMatrix
aPreMultiply(
549 basegfx::utils::createScaleTranslateB2DHomMatrix(
550 1.0, aUnitRange
.getHeight(), 0.0, aUnitRange
.getMinY()));
551 aUnitGradientToObject
= aUnitGradientToObject
* aPreMultiply
;
553 // create full color run, including all SpreadMethod variants
557 aUnitRange
.getMinX(),
558 aUnitRange
.getMaxX());
561 return createResult(std::move(aTargetColor
), std::move(aTargetOpacity
), aUnitGradientToObject
);
566 SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D(
567 const basegfx::B2DHomMatrix
& rGradientTransform
,
568 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
569 SvgGradientEntryVector
&& rGradientEntries
,
570 const basegfx::B2DPoint
& rStart
,
571 const basegfx::B2DPoint
& rEnd
,
572 bool bUseUnitCoordinates
,
573 SpreadMethod aSpreadMethod
)
574 : SvgGradientHelper(rGradientTransform
, rPolyPolygon
, std::move(rGradientEntries
), rStart
, bUseUnitCoordinates
, aSpreadMethod
),
577 // ensure Preconditions are checked
578 checkPreconditions();
581 SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D()
585 bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
587 const SvgGradientHelper
* pSvgGradientHelper
= dynamic_cast< const SvgGradientHelper
* >(&rPrimitive
);
589 if(pSvgGradientHelper
&& SvgGradientHelper::operator==(*pSvgGradientHelper
))
591 const SvgLinearGradientPrimitive2D
& rCompare
= static_cast< const SvgLinearGradientPrimitive2D
& >(rPrimitive
);
593 return (getEnd() == rCompare
.getEnd());
599 basegfx::B2DRange
SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D
& /*rViewInformation*/) const
601 // return ObjectRange
602 return getPolyPolygon().getB2DRange();
606 sal_uInt32
SvgLinearGradientPrimitive2D::getPrimitive2DID() const
608 return PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D
;
611 } // end of namespace drawinglayer::primitive2d
614 namespace drawinglayer::primitive2d
616 void SvgRadialGradientPrimitive2D::checkPreconditions()
619 SvgGradientHelper::checkPreconditions();
621 if(getCreatesContent())
624 if(basegfx::fTools::equalZero(getRadius()))
626 // fill with single color using last stop color
632 void SvgRadialGradientPrimitive2D::createAtom(
633 Primitive2DContainer
& rTargetColor
,
634 Primitive2DContainer
& rTargetOpacity
,
635 const SvgGradientEntry
& rFrom
,
636 const SvgGradientEntry
& rTo
,
637 sal_Int32 nOffsetFrom
,
638 sal_Int32 nOffsetTo
) const
640 // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
641 if(rFrom
.getOffset() == rTo
.getOffset())
643 OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
647 const double fScaleFrom(rFrom
.getOffset() + nOffsetFrom
);
648 const double fScaleTo(rTo
.getOffset() + nOffsetTo
);
652 const basegfx::B2DVector
aFocalVector(getFocal() - getStart());
653 const basegfx::B2DVector
aTranslateFrom(aFocalVector
* (maFocalLength
- fScaleFrom
));
654 const basegfx::B2DVector
aTranslateTo(aFocalVector
* (maFocalLength
- fScaleTo
));
656 rTargetColor
.push_back(
657 new SvgRadialAtomPrimitive2D(
658 rFrom
.getColor(), fScaleFrom
, aTranslateFrom
,
659 rTo
.getColor(), fScaleTo
, aTranslateTo
));
663 rTargetColor
.push_back(
664 new SvgRadialAtomPrimitive2D(
665 rFrom
.getColor(), fScaleFrom
,
666 rTo
.getColor(), fScaleTo
));
669 if(!getFullyOpaque())
671 const double fTransFrom(1.0 - rFrom
.getOpacity());
672 const double fTransTo(1.0 - rTo
.getOpacity());
673 const basegfx::BColor
aColorFrom(fTransFrom
, fTransFrom
, fTransFrom
);
674 const basegfx::BColor
aColorTo(fTransTo
, fTransTo
, fTransTo
);
678 const basegfx::B2DVector
aFocalVector(getFocal() - getStart());
679 const basegfx::B2DVector
aTranslateFrom(aFocalVector
* (maFocalLength
- fScaleFrom
));
680 const basegfx::B2DVector
aTranslateTo(aFocalVector
* (maFocalLength
- fScaleTo
));
682 rTargetOpacity
.push_back(
683 new SvgRadialAtomPrimitive2D(
684 aColorFrom
, fScaleFrom
, aTranslateFrom
,
685 aColorTo
, fScaleTo
, aTranslateTo
));
689 rTargetOpacity
.push_back(
690 new SvgRadialAtomPrimitive2D(
691 aColorFrom
, fScaleFrom
,
692 aColorTo
, fScaleTo
));
698 basegfx::B2DHomMatrix
SvgRadialGradientPrimitive2D::createUnitGradientToObjectTransformation() const
700 const basegfx::B2DRange
aPolyRange(getPolyPolygon().getB2DRange());
701 const double fPolyWidth(aPolyRange
.getWidth());
702 const double fPolyHeight(aPolyRange
.getHeight());
704 // create ObjectTransform based on polygon range
705 const basegfx::B2DHomMatrix
aObjectTransform(
706 basegfx::utils::createScaleTranslateB2DHomMatrix(
707 fPolyWidth
, fPolyHeight
,
708 aPolyRange
.getMinX(), aPolyRange
.getMinY()));
709 basegfx::B2DHomMatrix aUnitGradientToObject
;
711 if(getUseUnitCoordinates())
713 // interpret in unit coordinate system -> object aspect ratio will scale result
714 // create unit transform from unit vector to given linear gradient vector
715 aUnitGradientToObject
.scale(getRadius(), getRadius());
716 aUnitGradientToObject
.translate(getStart().getX(), getStart().getY());
718 if(!getGradientTransform().isIdentity())
720 aUnitGradientToObject
= getGradientTransform() * aUnitGradientToObject
;
723 // create full transform from unit gradient coordinates to object coordinates
724 // including the SvgGradient transformation
725 aUnitGradientToObject
= aObjectTransform
* aUnitGradientToObject
;
729 // interpret in object coordinate system -> object aspect ratio will not scale result
730 // use X-Axis with radius, it was already made relative to object width when coming from
732 const double fRadius((aObjectTransform
* basegfx::B2DVector(getRadius(), 0.0)).getLength());
733 const basegfx::B2DPoint
aStart(aObjectTransform
* getStart());
735 aUnitGradientToObject
.scale(fRadius
, fRadius
);
736 aUnitGradientToObject
.translate(aStart
.getX(), aStart
.getY());
738 aUnitGradientToObject
*= getGradientTransform();
741 return aUnitGradientToObject
;
744 Primitive2DReference
SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D
& /*rViewInformation*/) const
748 // fill with last existing color
749 return createSingleGradientEntryFill();
751 else if(getCreatesContent())
753 // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
754 // invisible, width and height to fill are not empty
755 basegfx::B2DHomMatrix
aUnitGradientToObject(createUnitGradientToObjectTransformation());
757 // create inverse from it
758 basegfx::B2DHomMatrix
aObjectToUnitGradient(aUnitGradientToObject
);
759 aObjectToUnitGradient
.invert();
761 // back-transform polygon to unit gradient coordinates and get
762 // UnitRage. This is the range the gradient has to cover
763 basegfx::B2DPolyPolygon
aUnitPoly(getPolyPolygon());
764 aUnitPoly
.transform(aObjectToUnitGradient
);
765 const basegfx::B2DRange
aUnitRange(aUnitPoly
.getB2DRange());
767 // create range which the gradient has to cover to cover the whole given geometry.
768 // For circle, go from 0.0 to max radius in all directions (the corners)
769 double fMax(basegfx::B2DVector(aUnitRange
.getMinimum()).getLength());
770 fMax
= std::max(fMax
, basegfx::B2DVector(aUnitRange
.getMaximum()).getLength());
771 fMax
= std::max(fMax
, basegfx::B2DVector(aUnitRange
.getMinX(), aUnitRange
.getMaxY()).getLength());
772 fMax
= std::max(fMax
, basegfx::B2DVector(aUnitRange
.getMaxX(), aUnitRange
.getMinY()).getLength());
774 // prepare result vectors
775 Primitive2DContainer aTargetColor
;
776 Primitive2DContainer aTargetOpacity
;
780 // prepare maFocalVector
783 const_cast< SvgRadialGradientPrimitive2D
* >(this)->maFocalLength
= fMax
;
786 // create full color run, including all SpreadMethod variants
794 return createResult(std::move(aTargetColor
), std::move(aTargetOpacity
), aUnitGradientToObject
, true);
799 SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
800 const basegfx::B2DHomMatrix
& rGradientTransform
,
801 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
802 SvgGradientEntryVector
&& rGradientEntries
,
803 const basegfx::B2DPoint
& rStart
,
805 bool bUseUnitCoordinates
,
806 SpreadMethod aSpreadMethod
,
807 const basegfx::B2DPoint
* pFocal
)
808 : SvgGradientHelper(rGradientTransform
, rPolyPolygon
, std::move(rGradientEntries
), rStart
, bUseUnitCoordinates
, aSpreadMethod
),
813 if(pFocal
&& !pFocal
->equal(getStart()))
818 // ensure Preconditions are checked
819 checkPreconditions();
822 SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D()
826 bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
828 const SvgGradientHelper
* pSvgGradientHelper
= dynamic_cast< const SvgGradientHelper
* >(&rPrimitive
);
830 if(!pSvgGradientHelper
|| !SvgGradientHelper::operator==(*pSvgGradientHelper
))
833 const SvgRadialGradientPrimitive2D
& rCompare
= static_cast< const SvgRadialGradientPrimitive2D
& >(rPrimitive
);
835 if(getRadius() == rCompare
.getRadius())
837 if(isFocalSet() == rCompare
.isFocalSet())
841 return getFocal() == rCompare
.getFocal();
853 basegfx::B2DRange
SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D
& /*rViewInformation*/) const
855 // return ObjectRange
856 return getPolyPolygon().getB2DRange();
860 sal_uInt32
SvgRadialGradientPrimitive2D::getPrimitive2DID() const
862 return PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D
;
865 } // end of namespace drawinglayer::primitive2d
868 // SvgLinearAtomPrimitive2D class
870 namespace drawinglayer::primitive2d
872 Primitive2DReference
SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D
& /*rViewInformation*/) const
874 const double fDelta(getOffsetB() - getOffsetA());
876 if(basegfx::fTools::equalZero(fDelta
))
879 // use one discrete unit for overlap (one pixel)
880 const double fDiscreteUnit(getDiscreteUnit());
882 // use color distance and discrete lengths to calculate step count
883 const sal_uInt32
nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta
, fDiscreteUnit
));
885 // HACK: Splitting a gradient into adjacent polygons with gradually changing color is silly.
886 // If antialiasing is used to draw them, the AA-ed adjacent edges won't line up perfectly
887 // because of the AA (see SkiaSalGraphicsImpl::mergePolyPolygonToPrevious()).
888 // Make the polygons a bit wider, so they the partial overlap "fixes" this.
889 const double fixup
= SkiaHelper::isVCLSkiaEnabled() ? fDiscreteUnit
/ 2 : 0;
891 // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this
892 // should be exactly 0.0 and 1.0, but there were cases when this gets clipped
893 // against the mask polygon which got numerically problematic.
894 // This change is unnecessary in that respect, but avoids that numerical havoc
895 // by at the same time doing no real harm AFAIK
896 // TTTT: Remove again when clipping is fixed (!)
898 // prepare polygon in needed width at start position (with discrete overlap)
899 const basegfx::B2DPolygon
aPolygon(
900 basegfx::utils::createPolygonFromRect(
902 getOffsetA() - fDiscreteUnit
,
903 -0.0001, // TTTT -> should be 0.0, see comment above
904 getOffsetA() + (fDelta
/ nSteps
) + fDiscreteUnit
+ fixup
,
905 1.0001))); // TTTT -> should be 1.0, see comment above
907 // prepare loop (inside to outside, [0.0 .. 1.0[)
908 double fUnitScale(0.0);
909 const double fUnitStep(1.0 / nSteps
);
911 Primitive2DContainer aContainer
;
912 aContainer
.resize(nSteps
);
913 for(sal_uInt32
a(0); a
< nSteps
; a
++, fUnitScale
+= fUnitStep
)
915 basegfx::B2DPolygon
aNew(aPolygon
);
917 aNew
.transform(basegfx::utils::createTranslateB2DHomMatrix(fDelta
* fUnitScale
, 0.0));
918 aContainer
[a
] = new PolyPolygonColorPrimitive2D(
919 basegfx::B2DPolyPolygon(aNew
),
920 basegfx::interpolate(getColorA(), getColorB(), fUnitScale
));
922 return new GroupPrimitive2D(std::move(aContainer
));
925 SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D(
926 const basegfx::BColor
& aColorA
, double fOffsetA
,
927 const basegfx::BColor
& aColorB
, double fOffsetB
)
933 if(mfOffsetA
> mfOffsetB
)
935 OSL_ENSURE(false, "Wrong offset order (!)");
936 std::swap(mfOffsetA
, mfOffsetB
);
940 bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
942 if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive
))
944 const SvgLinearAtomPrimitive2D
& rCompare
= static_cast< const SvgLinearAtomPrimitive2D
& >(rPrimitive
);
946 return (getColorA() == rCompare
.getColorA()
947 && getColorB() == rCompare
.getColorB()
948 && getOffsetA() == rCompare
.getOffsetA()
949 && getOffsetB() == rCompare
.getOffsetB());
956 sal_uInt32
SvgLinearAtomPrimitive2D::getPrimitive2DID() const
958 return PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D
;
961 } // end of namespace drawinglayer::primitive2d
964 // SvgRadialAtomPrimitive2D class
966 namespace drawinglayer::primitive2d
968 Primitive2DReference
SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D
& /*rViewInformation*/) const
970 const double fDeltaScale(getScaleB() - getScaleA());
972 if(basegfx::fTools::equalZero(fDeltaScale
))
975 // use one discrete unit for overlap (one pixel)
976 const double fDiscreteUnit(getDiscreteUnit());
978 // use color distance and discrete lengths to calculate step count
979 const sal_uInt32
nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale
, fDiscreteUnit
));
981 // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
982 double fUnitScale(0.0);
983 const double fUnitStep(1.0 / nSteps
);
985 Primitive2DContainer aContainer
;
986 aContainer
.resize(nSteps
);
987 for(sal_uInt32
a(0); a
< nSteps
; a
++, fUnitScale
+= fUnitStep
)
989 basegfx::B2DHomMatrix aTransform
;
990 const double fEndScale(getScaleB() - (fDeltaScale
* fUnitScale
));
994 const basegfx::B2DVector
aTranslate(
995 basegfx::interpolate(
1000 aTransform
= basegfx::utils::createScaleTranslateB2DHomMatrix(
1008 aTransform
= basegfx::utils::createScaleB2DHomMatrix(
1013 basegfx::B2DPolygon
aNew(basegfx::utils::createPolygonFromUnitCircle());
1015 aNew
.transform(aTransform
);
1016 aContainer
[a
] = new PolyPolygonColorPrimitive2D(
1017 basegfx::B2DPolyPolygon(aNew
),
1018 basegfx::interpolate(getColorB(), getColorA(), fUnitScale
));
1020 return new GroupPrimitive2D(std::move(aContainer
));
1023 SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1024 const basegfx::BColor
& aColorA
, double fScaleA
, const basegfx::B2DVector
& rTranslateA
,
1025 const basegfx::BColor
& aColorB
, double fScaleB
, const basegfx::B2DVector
& rTranslateB
)
1026 : maColorA(aColorA
),
1031 // check and evtl. set translations
1032 if(!rTranslateA
.equal(rTranslateB
))
1034 mpTranslate
.reset( new VectorPair(rTranslateA
, rTranslateB
) );
1037 // scale A and B have to be positive
1038 mfScaleA
= std::max(mfScaleA
, 0.0);
1039 mfScaleB
= std::max(mfScaleB
, 0.0);
1041 // scale B has to be bigger than scale A; swap if different
1042 if(mfScaleA
> mfScaleB
)
1044 OSL_ENSURE(false, "Wrong offset order (!)");
1045 std::swap(mfScaleA
, mfScaleB
);
1049 std::swap(mpTranslate
->maTranslateA
, mpTranslate
->maTranslateB
);
1054 SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1055 const basegfx::BColor
& aColorA
, double fScaleA
,
1056 const basegfx::BColor
& aColorB
, double fScaleB
)
1057 : maColorA(aColorA
),
1062 // scale A and B have to be positive
1063 mfScaleA
= std::max(mfScaleA
, 0.0);
1064 mfScaleB
= std::max(mfScaleB
, 0.0);
1066 // scale B has to be bigger than scale A; swap if different
1067 if(mfScaleA
> mfScaleB
)
1069 OSL_ENSURE(false, "Wrong offset order (!)");
1070 std::swap(mfScaleA
, mfScaleB
);
1074 SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D()
1078 bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
1080 if(!DiscreteMetricDependentPrimitive2D::operator==(rPrimitive
))
1083 const SvgRadialAtomPrimitive2D
& rCompare
= static_cast< const SvgRadialAtomPrimitive2D
& >(rPrimitive
);
1085 if(getColorA() == rCompare
.getColorA()
1086 && getColorB() == rCompare
.getColorB()
1087 && getScaleA() == rCompare
.getScaleA()
1088 && getScaleB() == rCompare
.getScaleB())
1090 if(isTranslateSet() && rCompare
.isTranslateSet())
1092 return (getTranslateA() == rCompare
.getTranslateA()
1093 && getTranslateB() == rCompare
.getTranslateB());
1095 else if(!isTranslateSet() && !rCompare
.isTranslateSet())
1104 // provide unique ID
1105 sal_uInt32
SvgRadialAtomPrimitive2D::getPrimitive2DID() const
1107 return PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D
;
1110 } // end of namespace
1112 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */