tdf#162786, tdf#161947: Add support for setuptools and pip
[LibreOffice.git] / drawinglayer / source / primitive2d / svggradientprimitive2d.cxx
blob4b8a5cb4ab869e32cce3b3eaab749a3cb82320ba
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
34 #include <cmath>
35 #include <utility>
36 #include <vcl/skia/SkiaHelper.hxx>
38 using namespace com::sun::star;
41 namespace
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));
48 if(nSteps)
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));
60 return nSteps;
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());
72 if(0 == nCount)
74 // no entries, done
75 OSL_ENSURE(false, "Single gradient entry construction without entry (!)");
76 return nullptr;
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
85 return nullptr;
88 if (basegfx::fTools::moreOrEqual(fOpacity, 1.0))
90 // no opacity
91 return Primitive2DReference {
92 new PolyPolygonColorPrimitive2D(
93 getPolyPolygon(),
94 rSingleEntry.getColor()) };
97 // if transparent, use PolyPolygonRGBAPrimitive2D
98 return Primitive2DReference {
99 new PolyPolygonRGBAPrimitive2D(
100 getPolyPolygon(),
101 rSingleEntry.getColor(),
102 1.0 - fOpacity) };
105 void SvgGradientHelper::checkPreconditions()
107 const SvgGradientEntryVector& rEntries = getGradientEntries();
109 if(rEntries.empty())
111 // no fill at all, done
112 return;
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()))
126 // invisible
127 mbFullyOpaque = false;
129 else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0))
131 // completely opaque
132 bAllInvisible = false;
134 else
136 // opacity
137 bAllInvisible = false;
138 mbFullyOpaque = false;
141 if(!basegfx::fTools::betweenOrEqualEither(rCandidate.getOffset(), 0.0, 1.0))
143 bInvalidEntries = true;
147 if(bAllInvisible)
149 // all invisible, nothing to do
150 return;
153 if(bInvalidEntries)
155 // invalid entries, do nothing
156 SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]");
157 return;
160 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
162 if(aPolyRange.isEmpty())
164 // no range to fill, nothing to do
165 return;
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
174 return;
177 mbCreatesContent = true;
179 if(1 == rEntries.size())
181 // fill with single existing color
182 setSingleEntry();
186 const SvgGradientEntry& SvgGradientHelper::FindEntryLessOrEqual(
187 sal_Int32& rInt,
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))
197 return *aIter;
201 // walk over gap to the left, be prepared for missing 0.0/1.0 entries
202 rInt--;
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(
209 sal_Int32& rInt,
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))
219 return *aIter;
223 // walk over gap to the right, be prepared for missing 0.0/1.0 entries
224 rInt++;
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,
244 double fStart,
245 double fEnd) const
247 double fInt(0.0);
248 double fFrac(0.0);
249 double fEnd2(0.0);
251 if(SpreadMethod::Pad == getSpreadMethod())
253 if(fStart < 0.0)
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();
262 if(fEnd > 1.0)
264 // change fEnd early, but create geometry later (after range below)
265 fEnd2 = fEnd;
266 fEnd = getGradientEntries().back().getOffset();
270 while(fStart < fEnd)
272 fFrac = std::modf(fStart, &fInt);
274 if(fFrac < 0.0)
276 fInt -= 1;
277 fFrac = 1.0 + fFrac;
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;
293 else
295 SAL_WARN("drawinglayer", "SvgGradientHelper spread error");
296 fStart += 1.0;
300 if(fEnd2 > 1.0)
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,
314 bool bInvert) const
316 Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert(bInvert));
317 Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert(bInvert));
319 if(aTargetColorEntries.empty())
320 return nullptr;
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 });
334 else
336 xRefContent = new TransformPrimitive2D(
337 rUnitGradientToObject,
338 std::move(aTargetColorEntries));
341 return new MaskPrimitive2D(
342 getPolyPolygon(),
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)),
356 maStart(rStart),
357 maSpreadMethod(aSpreadMethod),
358 mbCreatesContent(false),
359 mbSingleEntry(false),
360 mbFullyOpaque(true),
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())
382 return;
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()
418 // call parent
419 SvgGradientHelper::checkPreconditions();
421 if(getCreatesContent())
423 // Check Vector
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
429 setSingleEntry();
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 (!)");
447 else
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;
500 else
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
519 if(getSingleEntry())
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
554 createRun(
555 aTargetColor,
556 aTargetOpacity,
557 aUnitRange.getMinX(),
558 aUnitRange.getMaxX());
561 return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject);
563 return nullptr;
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),
575 maEnd(rEnd)
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());
596 return false;
599 basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
601 // return ObjectRange
602 return getPolyPolygon().getB2DRange();
605 // provide unique ID
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()
618 // call parent
619 SvgGradientHelper::checkPreconditions();
621 if(getCreatesContent())
623 // Check Radius
624 if(basegfx::fTools::equalZero(getRadius()))
626 // fill with single color using last stop color
627 setSingleEntry();
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 (!)");
645 else
647 const double fScaleFrom(rFrom.getOffset() + nOffsetFrom);
648 const double fScaleTo(rTo.getOffset() + nOffsetTo);
650 if(isFocalSet())
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));
661 else
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);
676 if(isFocalSet())
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));
687 else
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;
727 else
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
731 // SVG import
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
746 if(getSingleEntry())
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;
778 if(0.0 < fMax)
780 // prepare maFocalVector
781 if(isFocalSet())
783 const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax;
786 // create full color run, including all SpreadMethod variants
787 createRun(
788 aTargetColor,
789 aTargetOpacity,
790 0.0,
791 fMax);
794 return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject, true);
796 return nullptr;
799 SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
800 const basegfx::B2DHomMatrix& rGradientTransform,
801 const basegfx::B2DPolyPolygon& rPolyPolygon,
802 SvgGradientEntryVector&& rGradientEntries,
803 const basegfx::B2DPoint& rStart,
804 double fRadius,
805 bool bUseUnitCoordinates,
806 SpreadMethod aSpreadMethod,
807 const basegfx::B2DPoint* pFocal)
808 : SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod),
809 mfRadius(fRadius),
810 maFocal(rStart),
811 maFocalLength(0.0)
813 if(pFocal && !pFocal->equal(getStart()))
815 maFocal = *pFocal;
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))
831 return false;
833 const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);
835 if(getRadius() == rCompare.getRadius())
837 if(isFocalSet() == rCompare.isFocalSet())
839 if(isFocalSet())
841 return getFocal() == rCompare.getFocal();
843 else
845 return true;
850 return false;
853 basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
855 // return ObjectRange
856 return getPolyPolygon().getB2DRange();
859 // provide unique ID
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))
877 return nullptr;
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(
901 basegfx::B2DRange(
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)
928 : maColorA(aColorA),
929 maColorB(aColorB),
930 mfOffsetA(fOffsetA),
931 mfOffsetB(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());
952 return false;
955 // provide unique ID
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))
973 return nullptr;
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));
992 if(isTranslateSet())
994 const basegfx::B2DVector aTranslate(
995 basegfx::interpolate(
996 getTranslateB(),
997 getTranslateA(),
998 fUnitScale));
1000 aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
1001 fEndScale,
1002 fEndScale,
1003 aTranslate.getX(),
1004 aTranslate.getY());
1006 else
1008 aTransform = basegfx::utils::createScaleB2DHomMatrix(
1009 fEndScale,
1010 fEndScale);
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),
1027 maColorB(aColorB),
1028 mfScaleA(fScaleA),
1029 mfScaleB(fScaleB)
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);
1047 if(mpTranslate)
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),
1058 maColorB(aColorB),
1059 mfScaleA(fScaleA),
1060 mfScaleB(fScaleB)
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))
1081 return false;
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())
1097 return true;
1101 return false;
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: */