Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / drawinglayer / source / primitive2d / svggradientprimitive2d.cxx
blobdb3f4df3eb0076d2ca7be4f1fb0bd8ba92bf1714
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/polypolygonprimitive2d.hxx>
23 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
25 #include <basegfx/matrix/b2dhommatrixtools.hxx>
26 #include <basegfx/polygon/b2dpolygontools.hxx>
27 #include <basegfx/polygon/b2dpolygon.hxx>
28 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
31 #include <drawinglayer/geometry/viewinformation2d.hxx>
34 using namespace com::sun::star;
37 namespace
39 sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, const basegfx::BColor& rColorB, double fDelta, double fDiscreteUnit)
41 // use color distance, assume to do every color step (full quality)
42 sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
44 if(nSteps)
46 // calc discrete length to change color all 1.5 discrete units (pixels)
47 const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5)));
49 nSteps = std::min(nSteps, nDistSteps);
52 // roughly cut when too big or too small
53 nSteps = std::min(nSteps, sal_uInt32(255));
54 nSteps = std::max(nSteps, sal_uInt32(1));
56 return nSteps;
58 } // end of anonymous namespace
61 namespace drawinglayer
63 namespace primitive2d
65 void SvgGradientHelper::createSingleGradientEntryFill(Primitive2DContainer& rContainer) const
67 const SvgGradientEntryVector& rEntries = getGradientEntries();
68 const sal_uInt32 nCount(rEntries.size());
70 if(nCount)
72 const SvgGradientEntry& rSingleEntry = rEntries[nCount - 1];
73 const double fOpacity(rSingleEntry.getOpacity());
75 if(fOpacity > 0.0)
77 Primitive2DReference xRef(
78 new PolyPolygonColorPrimitive2D(
79 getPolyPolygon(),
80 rSingleEntry.getColor()));
82 if(fOpacity < 1.0)
84 const Primitive2DContainer aContent { xRef };
86 xRef = Primitive2DReference(
87 new UnifiedTransparencePrimitive2D(
88 aContent,
89 1.0 - fOpacity));
92 rContainer.push_back(xRef);
95 else
97 OSL_ENSURE(false, "Single gradient entry construction without entry (!)");
101 void SvgGradientHelper::checkPreconditions()
103 mbPreconditionsChecked = true;
104 const SvgGradientEntryVector& rEntries = getGradientEntries();
106 if(rEntries.empty())
108 // no fill at all
110 else
112 const sal_uInt32 nCount(rEntries.size());
114 if(1 == nCount)
116 // fill with single existing color
117 setSingleEntry();
119 else
121 // sort maGradientEntries when more than one
122 std::sort(maGradientEntries.begin(), maGradientEntries.end());
124 // gradient with at least two colors
125 bool bAllInvisible(true);
127 for(sal_uInt32 a(0); a < nCount; a++)
129 const SvgGradientEntry& rCandidate = rEntries[a];
131 if(basegfx::fTools::equalZero(rCandidate.getOpacity()))
133 // invisible
134 mbFullyOpaque = false;
136 else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0))
138 // completely opaque
139 bAllInvisible = false;
141 else
143 // opacity
144 bAllInvisible = false;
145 mbFullyOpaque = false;
149 if(bAllInvisible)
151 // all invisible, nothing to do
153 else
155 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
157 if(aPolyRange.isEmpty())
159 // no range to fill, nothing to do
161 else
163 const double fPolyWidth(aPolyRange.getWidth());
164 const double fPolyHeight(aPolyRange.getHeight());
166 if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight))
168 // no width/height to fill, nothing to do
170 else
172 mbCreatesContent = true;
180 double SvgGradientHelper::createRun(
181 Primitive2DContainer& rTargetColor,
182 Primitive2DContainer& rTargetOpacity,
183 double fPos,
184 double fMax,
185 const SvgGradientEntryVector& rEntries,
186 sal_Int32 nOffset) const
188 const sal_uInt32 nCount(rEntries.size());
190 if(nCount)
192 const SvgGradientEntry& rStart = rEntries[0];
193 const bool bCreateStartPad(fPos < 0.0 && SpreadMethod::Pad == getSpreadMethod());
194 const bool bCreateStartFill(rStart.getOffset() > 0.0);
195 sal_uInt32 nIndex(0);
197 if(bCreateStartPad || bCreateStartFill)
199 const SvgGradientEntry aTemp(bCreateStartPad ? fPos : 0.0, rStart.getColor(), rStart.getOpacity());
201 createAtom(rTargetColor, rTargetOpacity, aTemp, rStart, nOffset);
202 fPos = rStart.getOffset();
205 while(fPos < 1.0 && nIndex + 1 < nCount)
207 const SvgGradientEntry& rCandidateA = rEntries[nIndex++];
208 const SvgGradientEntry& rCandidateB = rEntries[nIndex];
210 createAtom(rTargetColor, rTargetOpacity, rCandidateA, rCandidateB, nOffset);
211 fPos = rCandidateB.getOffset();
214 const SvgGradientEntry& rEnd = rEntries[nCount - 1];
215 const bool bCreateEndPad(fPos < fMax && SpreadMethod::Pad == getSpreadMethod());
216 const bool bCreateEndFill(rEnd.getOffset() < 1.0);
218 if(bCreateEndPad || bCreateEndFill)
220 fPos = bCreateEndPad ? fMax : 1.0;
221 const SvgGradientEntry aTemp(fPos, rEnd.getColor(), rEnd.getOpacity());
223 createAtom(rTargetColor, rTargetOpacity, rEnd, aTemp, nOffset);
226 else
228 OSL_ENSURE(false, "GradientAtom creation without ColorStops (!)");
229 fPos = fMax;
232 return fPos;
235 void SvgGradientHelper::createResult(
236 Primitive2DContainer& rContainer,
237 const Primitive2DContainer& rTargetColor,
238 const Primitive2DContainer& rTargetOpacity,
239 const basegfx::B2DHomMatrix& rUnitGradientToObject,
240 bool bInvert) const
242 const Primitive2DContainer aTargetColorEntries(rTargetColor.maybeInvert(bInvert));
243 const Primitive2DContainer aTargetOpacityEntries(rTargetOpacity.maybeInvert(bInvert));
245 if(!aTargetColorEntries.empty())
247 Primitive2DReference xRefContent;
249 if(!aTargetOpacityEntries.empty())
251 const Primitive2DReference xRefOpacity = new TransparencePrimitive2D(
252 aTargetColorEntries,
253 aTargetOpacityEntries);
255 xRefContent = new TransformPrimitive2D(
256 rUnitGradientToObject,
257 Primitive2DContainer { xRefOpacity });
259 else
261 xRefContent = new TransformPrimitive2D(
262 rUnitGradientToObject,
263 aTargetColorEntries);
266 rContainer.push_back(new MaskPrimitive2D(
267 getPolyPolygon(),
268 Primitive2DContainer { xRefContent }));
272 SvgGradientHelper::SvgGradientHelper(
273 const basegfx::B2DHomMatrix& rGradientTransform,
274 const basegfx::B2DPolyPolygon& rPolyPolygon,
275 const SvgGradientEntryVector& rGradientEntries,
276 const basegfx::B2DPoint& rStart,
277 bool bUseUnitCoordinates,
278 SpreadMethod aSpreadMethod)
279 : maGradientTransform(rGradientTransform),
280 maPolyPolygon(rPolyPolygon),
281 maGradientEntries(rGradientEntries),
282 maStart(rStart),
283 maSpreadMethod(aSpreadMethod),
284 mbPreconditionsChecked(false),
285 mbCreatesContent(false),
286 mbSingleEntry(false),
287 mbFullyOpaque(true),
288 mbUseUnitCoordinates(bUseUnitCoordinates)
292 SvgGradientHelper::~SvgGradientHelper()
296 bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const
298 const SvgGradientHelper& rCompare = rSvgGradientHelper;
300 return (getGradientTransform() == rCompare.getGradientTransform()
301 && getPolyPolygon() == rCompare.getPolyPolygon()
302 && getGradientEntries() == rCompare.getGradientEntries()
303 && getStart() == rCompare.getStart()
304 && getUseUnitCoordinates() == rCompare.getUseUnitCoordinates()
305 && getSpreadMethod() == rCompare.getSpreadMethod());
308 } // end of namespace primitive2d
309 } // end of namespace drawinglayer
312 namespace drawinglayer
314 namespace primitive2d
316 void SvgLinearGradientPrimitive2D::checkPreconditions()
318 // call parent
319 SvgGradientHelper::checkPreconditions();
321 if(getCreatesContent())
323 // Check Vector
324 const basegfx::B2DVector aVector(getEnd() - getStart());
326 if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY()))
328 // fill with single color using last stop color
329 setSingleEntry();
334 void SvgLinearGradientPrimitive2D::createAtom(
335 Primitive2DContainer& rTargetColor,
336 Primitive2DContainer& rTargetOpacity,
337 const SvgGradientEntry& rFrom,
338 const SvgGradientEntry& rTo,
339 sal_Int32 nOffset) const
341 // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
342 if(rFrom.getOffset() == rTo.getOffset())
344 OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
346 else
348 rTargetColor.push_back(
349 new SvgLinearAtomPrimitive2D(
350 rFrom.getColor(), rFrom.getOffset() + nOffset,
351 rTo.getColor(), rTo.getOffset() + nOffset));
353 if(!getFullyOpaque())
355 const double fTransFrom(1.0 - rFrom.getOpacity());
356 const double fTransTo(1.0 - rTo.getOpacity());
357 const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
358 const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);
360 rTargetOpacity.push_back(
361 new SvgLinearAtomPrimitive2D(
362 aColorFrom, rFrom.getOffset() + nOffset,
363 aColorTo, rTo.getOffset() + nOffset));
368 void SvgLinearGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
370 if(!getPreconditionsChecked())
372 const_cast< SvgLinearGradientPrimitive2D* >(this)->checkPreconditions();
375 if(getSingleEntry())
377 // fill with last existing color
378 createSingleGradientEntryFill(rContainer);
380 else if(getCreatesContent())
382 // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
383 // invisible, width and height to fill are not empty
384 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
385 const double fPolyWidth(aPolyRange.getWidth());
386 const double fPolyHeight(aPolyRange.getHeight());
388 // create ObjectTransform based on polygon range
389 const basegfx::B2DHomMatrix aObjectTransform(
390 basegfx::utils::createScaleTranslateB2DHomMatrix(
391 fPolyWidth, fPolyHeight,
392 aPolyRange.getMinX(), aPolyRange.getMinY()));
393 basegfx::B2DHomMatrix aUnitGradientToObject;
395 if(getUseUnitCoordinates())
397 // interpret in unit coordinate system -> object aspect ratio will scale result
398 // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given
399 // gradient vector defined by Start,End
400 const basegfx::B2DVector aVector(getEnd() - getStart());
401 const double fVectorLength(aVector.getLength());
403 aUnitGradientToObject.scale(fVectorLength, 1.0);
404 aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX()));
405 aUnitGradientToObject.translate(getStart().getX(), getStart().getY());
407 aUnitGradientToObject *= getGradientTransform();
409 // create full transform from unit gradient coordinates to object coordinates
410 // including the SvgGradient transformation
411 aUnitGradientToObject *= aObjectTransform;
413 else
415 // interpret in object coordinate system -> object aspect ratio will not scale result
416 const basegfx::B2DPoint aStart(aObjectTransform * getStart());
417 const basegfx::B2DPoint aEnd(aObjectTransform * getEnd());
418 const basegfx::B2DVector aVector(aEnd - aStart);
420 aUnitGradientToObject.scale(aVector.getLength(), 1.0);
421 aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX()));
422 aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
424 aUnitGradientToObject *= getGradientTransform();
427 // create inverse from it
428 basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
429 aObjectToUnitGradient.invert();
431 // back-transform polygon to unit gradient coordinates and get
432 // UnitRage. This is the range the gradient has to cover
433 basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
434 aUnitPoly.transform(aObjectToUnitGradient);
435 const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
437 // prepare result vectors
438 Primitive2DContainer aTargetColor;
439 Primitive2DContainer aTargetOpacity;
441 if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0))
443 // add a pre-multiply to aUnitGradientToObject to allow
444 // multiplication of the polygon(xl, 0.0, xr, 1.0)
445 const basegfx::B2DHomMatrix aPreMultiply(
446 basegfx::utils::createScaleTranslateB2DHomMatrix(
447 1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY()));
448 aUnitGradientToObject = aUnitGradientToObject * aPreMultiply;
450 // create central run, may also already do all necessary when
451 // SpreadMethod::Pad is set as SpreadMethod and/or the range is smaller
452 double fPos(createRun(aTargetColor, aTargetOpacity, aUnitRange.getMinX(), aUnitRange.getMaxX(), getGradientEntries(), 0));
454 if(fPos < aUnitRange.getMaxX())
456 // can only happen when SpreadMethod is SpreadMethod::Reflect or SpreadMethod::Repeat,
457 // else the start and end pads are already created and fPos == aUnitRange.getMaxX().
458 // Its possible to express the repeated linear gradient by adding the
459 // transformed central run. Create it this way
460 Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert());
461 Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert());
462 aTargetColor.clear();
463 aTargetOpacity.clear();
465 if(!aTargetColorEntries.empty())
467 // add original central run as group primitive
468 aTargetColor.push_back(new GroupPrimitive2D(aTargetColorEntries));
470 if(!aTargetOpacityEntries.empty())
472 aTargetOpacity.push_back(new GroupPrimitive2D(aTargetOpacityEntries));
475 // add negative runs
476 fPos = 0.0;
477 sal_Int32 nOffset(0);
479 while(fPos > aUnitRange.getMinX())
481 fPos -= 1.0;
482 nOffset++;
484 basegfx::B2DHomMatrix aTransform;
485 const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && (nOffset % 2));
487 if(bMirror)
489 aTransform.scale(-1.0, 1.0);
490 aTransform.translate(fPos + 1.0, 0.0);
492 else
494 aTransform.translate(fPos, 0.0);
497 aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
499 if(!aTargetOpacityEntries.empty())
501 aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
505 // add positive runs
506 fPos = 1.0;
507 nOffset = 1;
509 while(fPos < aUnitRange.getMaxX())
511 basegfx::B2DHomMatrix aTransform;
512 const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && (nOffset % 2));
514 if(bMirror)
516 aTransform.scale(-1.0, 1.0);
517 aTransform.translate(fPos + 1.0, 0.0);
519 else
521 aTransform.translate(fPos, 0.0);
524 aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
526 if(!aTargetOpacityEntries.empty())
528 aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
531 fPos += 1.0;
532 nOffset++;
538 createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject);
542 SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D(
543 const basegfx::B2DHomMatrix& rGradientTransform,
544 const basegfx::B2DPolyPolygon& rPolyPolygon,
545 const SvgGradientEntryVector& rGradientEntries,
546 const basegfx::B2DPoint& rStart,
547 const basegfx::B2DPoint& rEnd,
548 bool bUseUnitCoordinates,
549 SpreadMethod aSpreadMethod)
550 : BufferedDecompositionPrimitive2D(),
551 SvgGradientHelper(rGradientTransform, rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod),
552 maEnd(rEnd)
556 SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D()
560 bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
562 const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
564 if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
566 const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive);
568 return (getEnd() == rCompare.getEnd());
571 return false;
574 basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
576 // return ObjectRange
577 return getPolyPolygon().getB2DRange();
580 // provide unique ID
581 ImplPrimitive2DIDBlock(SvgLinearGradientPrimitive2D, PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D)
583 } // end of namespace primitive2d
584 } // end of namespace drawinglayer
587 namespace drawinglayer
589 namespace primitive2d
591 void SvgRadialGradientPrimitive2D::checkPreconditions()
593 // call parent
594 SvgGradientHelper::checkPreconditions();
596 if(getCreatesContent())
598 // Check Radius
599 if(basegfx::fTools::equalZero(getRadius()))
601 // fill with single color using last stop color
602 setSingleEntry();
607 void SvgRadialGradientPrimitive2D::createAtom(
608 Primitive2DContainer& rTargetColor,
609 Primitive2DContainer& rTargetOpacity,
610 const SvgGradientEntry& rFrom,
611 const SvgGradientEntry& rTo,
612 sal_Int32 nOffset) const
614 // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
615 if(rFrom.getOffset() == rTo.getOffset())
617 OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
619 else
621 const double fScaleFrom(rFrom.getOffset() + nOffset);
622 const double fScaleTo(rTo.getOffset() + nOffset);
624 if(isFocalSet())
626 const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
627 const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
629 rTargetColor.push_back(
630 new SvgRadialAtomPrimitive2D(
631 rFrom.getColor(), fScaleFrom, aTranslateFrom,
632 rTo.getColor(), fScaleTo, aTranslateTo));
634 else
636 rTargetColor.push_back(
637 new SvgRadialAtomPrimitive2D(
638 rFrom.getColor(), fScaleFrom,
639 rTo.getColor(), fScaleTo));
642 if(!getFullyOpaque())
644 const double fTransFrom(1.0 - rFrom.getOpacity());
645 const double fTransTo(1.0 - rTo.getOpacity());
646 const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
647 const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);
649 if(isFocalSet())
651 const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
652 const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
654 rTargetOpacity.push_back(
655 new SvgRadialAtomPrimitive2D(
656 aColorFrom, fScaleFrom, aTranslateFrom,
657 aColorTo, fScaleTo, aTranslateTo));
659 else
661 rTargetOpacity.push_back(
662 new SvgRadialAtomPrimitive2D(
663 aColorFrom, fScaleFrom,
664 aColorTo, fScaleTo));
670 const SvgGradientEntryVector& SvgRadialGradientPrimitive2D::getMirroredGradientEntries() const
672 if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
674 const_cast< SvgRadialGradientPrimitive2D* >(this)->createMirroredGradientEntries();
677 return maMirroredGradientEntries;
680 void SvgRadialGradientPrimitive2D::createMirroredGradientEntries()
682 if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
684 const sal_uInt32 nCount(getGradientEntries().size());
685 maMirroredGradientEntries.clear();
686 maMirroredGradientEntries.reserve(nCount);
688 for(sal_uInt32 a(0); a < nCount; a++)
690 const SvgGradientEntry& rCandidate = getGradientEntries()[nCount - 1 - a];
692 maMirroredGradientEntries.emplace_back(
693 1.0 - rCandidate.getOffset(),
694 rCandidate.getColor(),
695 rCandidate.getOpacity());
700 void SvgRadialGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
702 if(!getPreconditionsChecked())
704 const_cast< SvgRadialGradientPrimitive2D* >(this)->checkPreconditions();
707 if(getSingleEntry())
709 // fill with last existing color
710 createSingleGradientEntryFill(rContainer);
712 else if(getCreatesContent())
714 // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
715 // invisible, width and height to fill are not empty
716 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
717 const double fPolyWidth(aPolyRange.getWidth());
718 const double fPolyHeight(aPolyRange.getHeight());
720 // create ObjectTransform based on polygon range
721 const basegfx::B2DHomMatrix aObjectTransform(
722 basegfx::utils::createScaleTranslateB2DHomMatrix(
723 fPolyWidth, fPolyHeight,
724 aPolyRange.getMinX(), aPolyRange.getMinY()));
725 basegfx::B2DHomMatrix aUnitGradientToObject;
727 if(getUseUnitCoordinates())
729 // interpret in unit coordinate system -> object aspect ratio will scale result
730 // create unit transform from unit vector to given linear gradient vector
731 aUnitGradientToObject.scale(getRadius(), getRadius());
732 aUnitGradientToObject.translate(getStart().getX(), getStart().getY());
734 if(!getGradientTransform().isIdentity())
736 aUnitGradientToObject = getGradientTransform() * aUnitGradientToObject;
739 // create full transform from unit gradient coordinates to object coordinates
740 // including the SvgGradient transformation
741 aUnitGradientToObject = aObjectTransform * aUnitGradientToObject;
743 else
745 // interpret in object coordinate system -> object aspect ratio will not scale result
746 // use X-Axis with radius, it was already made relative to object width when coming from
747 // SVG import
748 const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength());
749 const basegfx::B2DPoint aStart(aObjectTransform * getStart());
751 aUnitGradientToObject.scale(fRadius, fRadius);
752 aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
754 aUnitGradientToObject *= getGradientTransform();
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 central run, may also already do all necessary when
787 // SpreadMethod::Pad is set as SpreadMethod and/or the range is smaller
788 double fPos(createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), 0));
790 if(fPos < fMax)
792 // can only happen when SpreadMethod is SpreadMethod::Reflect or SpreadMethod::Repeat,
793 // else the start and end pads are already created and fPos == fMax.
794 // For radial there is no way to transform the already created
795 // central run, it needs to be created from 1.0 to fMax
796 sal_Int32 nOffset(1);
798 while(fPos < fMax)
800 const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && (nOffset % 2));
802 if(bMirror)
804 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getMirroredGradientEntries(), nOffset);
806 else
808 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), nOffset);
811 nOffset++;
812 fPos += 1.0;
817 createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject, true);
821 SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
822 const basegfx::B2DHomMatrix& rGradientTransform,
823 const basegfx::B2DPolyPolygon& rPolyPolygon,
824 const SvgGradientEntryVector& rGradientEntries,
825 const basegfx::B2DPoint& rStart,
826 double fRadius,
827 bool bUseUnitCoordinates,
828 SpreadMethod aSpreadMethod,
829 const basegfx::B2DPoint* pFocal)
830 : BufferedDecompositionPrimitive2D(),
831 SvgGradientHelper(rGradientTransform, rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod),
832 mfRadius(fRadius),
833 maFocal(rStart),
834 maFocalVector(0.0, 0.0),
835 maFocalLength(0.0),
836 maMirroredGradientEntries(),
837 mbFocalSet(false)
839 if(pFocal && !pFocal->equal(getStart()))
841 maFocal = *pFocal;
842 maFocalVector = maFocal - getStart();
843 mbFocalSet = true;
847 SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D()
851 bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
853 const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
855 if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
857 const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);
859 if(getRadius() == rCompare.getRadius())
861 if(isFocalSet() == rCompare.isFocalSet())
863 if(isFocalSet())
865 return getFocal() == rCompare.getFocal();
867 else
869 return true;
875 return false;
878 basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
880 // return ObjectRange
881 return getPolyPolygon().getB2DRange();
884 // provide unique ID
885 ImplPrimitive2DIDBlock(SvgRadialGradientPrimitive2D, PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D)
887 } // end of namespace primitive2d
888 } // end of namespace drawinglayer
891 // SvgLinearAtomPrimitive2D class
893 namespace drawinglayer
895 namespace primitive2d
897 void SvgLinearAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
899 const double fDelta(getOffsetB() - getOffsetA());
901 if(!basegfx::fTools::equalZero(fDelta))
903 // use one discrete unit for overlap (one pixel)
904 const double fDiscreteUnit(getDiscreteUnit());
906 // use color distance and discrete lengths to calculate step count
907 const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit));
909 // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this
910 // should be exactly 0.0 and 1.0, but there were cases when this gets clipped
911 // against the mask polygon which got numerically problematic.
912 // This change is unnecessary in that respect, but avoids that numerical havoc
913 // by at the same time doing no real harm AFAIK
914 // TTTT: Remove again when clipping is fixed (!)
916 // prepare polygon in needed width at start position (with discrete overlap)
917 const basegfx::B2DPolygon aPolygon(
918 basegfx::utils::createPolygonFromRect(
919 basegfx::B2DRange(
920 getOffsetA() - fDiscreteUnit,
921 -0.0001, // TTTT -> should be 0.0, see comment above
922 getOffsetA() + (fDelta / nSteps) + fDiscreteUnit,
923 1.0001))); // TTTT -> should be 1.0, see comment above
925 // prepare loop (inside to outside, [0.0 .. 1.0[)
926 double fUnitScale(0.0);
927 const double fUnitStep(1.0 / nSteps);
929 for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
931 basegfx::B2DPolygon aNew(aPolygon);
933 aNew.transform(basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
934 rContainer.push_back(new PolyPolygonColorPrimitive2D(
935 basegfx::B2DPolyPolygon(aNew),
936 basegfx::interpolate(getColorA(), getColorB(), fUnitScale)));
941 SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D(
942 const basegfx::BColor& aColorA, double fOffsetA,
943 const basegfx::BColor& aColorB, double fOffsetB)
944 : DiscreteMetricDependentPrimitive2D(),
945 maColorA(aColorA),
946 maColorB(aColorB),
947 mfOffsetA(fOffsetA),
948 mfOffsetB(fOffsetB)
950 if(mfOffsetA > mfOffsetB)
952 OSL_ENSURE(false, "Wrong offset order (!)");
953 std::swap(mfOffsetA, mfOffsetB);
957 bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
959 if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
961 const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive);
963 return (getColorA() == rCompare.getColorA()
964 && getColorB() == rCompare.getColorB()
965 && getOffsetA() == rCompare.getOffsetA()
966 && getOffsetB() == rCompare.getOffsetB());
969 return false;
972 // provide unique ID
973 ImplPrimitive2DIDBlock(SvgLinearAtomPrimitive2D, PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D)
975 } // end of namespace primitive2d
976 } // end of namespace drawinglayer
979 // SvgRadialAtomPrimitive2D class
981 namespace drawinglayer
983 namespace primitive2d
985 void SvgRadialAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
987 const double fDeltaScale(getScaleB() - getScaleA());
989 if(!basegfx::fTools::equalZero(fDeltaScale))
991 // use one discrete unit for overlap (one pixel)
992 const double fDiscreteUnit(getDiscreteUnit());
994 // use color distance and discrete lengths to calculate step count
995 const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit));
997 // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
998 double fUnitScale(0.0);
999 const double fUnitStep(1.0 / nSteps);
1001 for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1003 basegfx::B2DHomMatrix aTransform;
1004 const double fEndScale(getScaleB() - (fDeltaScale * fUnitScale));
1006 if(isTranslateSet())
1008 const basegfx::B2DVector aTranslate(
1009 basegfx::interpolate(
1010 getTranslateB(),
1011 getTranslateA(),
1012 fUnitScale));
1014 aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
1015 fEndScale,
1016 fEndScale,
1017 aTranslate.getX(),
1018 aTranslate.getY());
1020 else
1022 aTransform = basegfx::utils::createScaleB2DHomMatrix(
1023 fEndScale,
1024 fEndScale);
1027 basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
1029 aNew.transform(aTransform);
1030 rContainer.push_back(new PolyPolygonColorPrimitive2D(
1031 basegfx::B2DPolyPolygon(aNew),
1032 basegfx::interpolate(getColorB(), getColorA(), fUnitScale)));
1037 SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1038 const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA,
1039 const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB)
1040 : DiscreteMetricDependentPrimitive2D(),
1041 maColorA(aColorA),
1042 maColorB(aColorB),
1043 mfScaleA(fScaleA),
1044 mfScaleB(fScaleB)
1046 // check and evtl. set translations
1047 if(!rTranslateA.equal(rTranslateB))
1049 mpTranslate.reset( new VectorPair(rTranslateA, rTranslateB) );
1052 // scale A and B have to be positive
1053 mfScaleA = std::max(mfScaleA, 0.0);
1054 mfScaleB = std::max(mfScaleB, 0.0);
1056 // scale B has to be bigger than scale A; swap if different
1057 if(mfScaleA > mfScaleB)
1059 OSL_ENSURE(false, "Wrong offset order (!)");
1060 std::swap(mfScaleA, mfScaleB);
1062 if(mpTranslate)
1064 std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB);
1069 SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1070 const basegfx::BColor& aColorA, double fScaleA,
1071 const basegfx::BColor& aColorB, double fScaleB)
1072 : DiscreteMetricDependentPrimitive2D(),
1073 maColorA(aColorA),
1074 maColorB(aColorB),
1075 mfScaleA(fScaleA),
1076 mfScaleB(fScaleB)
1078 // scale A and B have to be positive
1079 mfScaleA = std::max(mfScaleA, 0.0);
1080 mfScaleB = std::max(mfScaleB, 0.0);
1082 // scale B has to be bigger than scale A; swap if different
1083 if(mfScaleA > mfScaleB)
1085 OSL_ENSURE(false, "Wrong offset order (!)");
1086 std::swap(mfScaleA, mfScaleB);
1090 SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D()
1094 bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
1096 if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
1098 const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive);
1100 if(getColorA() == rCompare.getColorA()
1101 && getColorB() == rCompare.getColorB()
1102 && getScaleA() == rCompare.getScaleA()
1103 && getScaleB() == rCompare.getScaleB())
1105 if(isTranslateSet() && rCompare.isTranslateSet())
1107 return (getTranslateA() == rCompare.getTranslateA()
1108 && getTranslateB() == rCompare.getTranslateB());
1110 else if(!isTranslateSet() && !rCompare.isTranslateSet())
1112 return true;
1117 return false;
1120 // provide unique ID
1121 ImplPrimitive2DIDBlock(SvgRadialAtomPrimitive2D, PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D)
1123 } // end of namespace primitive2d
1124 } // end of namespace drawinglayer
1126 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */