LibreOffice Module basegfx (master) 1
bgradient.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 */
9
12#include <com/sun/star/awt/Gradient2.hpp>
13#include <boost/property_tree/json_parser.hpp>
14#include <map>
15
16typedef std::map<OUString, OUString> StringMap;
17
18namespace
19{
20css::awt::GradientStyle lcl_getStyleFromString(std::u16string_view rStyle)
21{
22 if (rStyle == u"LINEAR")
23 return css::awt::GradientStyle_LINEAR;
24 else if (rStyle == u"AXIAL")
25 return css::awt::GradientStyle_AXIAL;
26 else if (rStyle == u"RADIAL")
27 return css::awt::GradientStyle_RADIAL;
28 else if (rStyle == u"ELLIPTICAL")
29 return css::awt::GradientStyle_ELLIPTICAL;
30 else if (rStyle == u"SQUARE")
31 return css::awt::GradientStyle_SQUARE;
32 else if (rStyle == u"RECT")
33 return css::awt::GradientStyle_RECT;
34
35 return css::awt::GradientStyle_LINEAR;
36}
37
38StringMap lcl_jsonToStringMap(std::u16string_view rJSON)
39{
40 StringMap aArgs;
41 if (rJSON.size() && rJSON[0] != '\0')
42 {
43 std::stringstream aStream(std::string(OUStringToOString(rJSON, RTL_TEXTENCODING_ASCII_US)));
44 boost::property_tree::ptree aTree;
45 boost::property_tree::read_json(aStream, aTree);
46
47 for (const auto& rPair : aTree)
48 {
49 aArgs[OUString::fromUtf8(rPair.first)]
50 = OUString::fromUtf8(rPair.second.get_value<std::string>("."));
51 }
52 }
53 return aArgs;
54}
55
56basegfx::BGradient lcl_buildGradientFromStringMap(StringMap& rMap)
57{
58 basegfx::BGradient aGradient(
59 basegfx::BColorStops(ColorToBColorConverter(rMap["startcolor"].toInt32(16)).getBColor(),
60 ColorToBColorConverter(rMap["endcolor"].toInt32(16)).getBColor()));
61
62 aGradient.SetGradientStyle(lcl_getStyleFromString(rMap["style"]));
63 aGradient.SetAngle(Degree10(rMap["angle"].toInt32()));
64
65 return aGradient;
66}
67}
68
69namespace basegfx
70{
71void BColorStops::setColorStopSequence(const css::awt::ColorStopSequence& rColorStops)
72{
73 const sal_Int32 nLen(rColorStops.getLength());
74
75 if (0 != nLen)
76 {
77 // we have ColorStops
78 reserve(nLen);
79 const css::awt::ColorStop* pSourceColorStop(rColorStops.getConstArray());
80
81 for (sal_Int32 a(0); a < nLen; a++, pSourceColorStop++)
82 {
83 emplace_back(pSourceColorStop->StopOffset,
84 BColor(pSourceColorStop->StopColor.Red, pSourceColorStop->StopColor.Green,
85 pSourceColorStop->StopColor.Blue));
86 }
87 }
88}
89
90BColorStops::BColorStops(const css::awt::ColorStopSequence& rColorStops)
91{
92 setColorStopSequence(rColorStops);
93}
94
95BColorStops::BColorStops(const css::uno::Any& rVal)
96{
97 if (rVal.has<css::awt::ColorStopSequence>())
98 {
99 // we can use awt::ColorStopSequence
100 css::awt::ColorStopSequence aColorStopSequence;
101 rVal >>= aColorStopSequence;
102 setColorStopSequence(aColorStopSequence);
103 }
104}
105
106// constructor with two colors to explicitly create a
107// BColorStops for a single StartColor @0.0 & EndColor @1.0
108BColorStops::BColorStops(const BColor& rStart, const BColor& rEnd)
109{
110 emplace_back(0.0, rStart);
111 emplace_back(1.0, rEnd);
112}
113
114/* Helper to grep the correct ColorStop out of
115 ColorStops and interpolate as needed for given
116 relative value in fPosition in the range of [0.0 .. 1.0].
117 It also takes care of evtl. given RequestedSteps.
118 */
119BColor BColorStops::getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps,
120 BColorStopRange& rLastColorStopRange) const
121{
122 // no color at all, done
123 if (empty())
124 return BColor();
125
126 // outside range -> at start
127 const double fMin(front().getStopOffset());
128 if (fPosition < fMin)
129 return front().getStopColor();
130
131 // outside range -> at end
132 const double fMax(back().getStopOffset());
133 if (fPosition > fMax)
134 return back().getStopColor();
135
136 // special case for the 'classic' case with just two colors:
137 // we can optimize that and keep the speed/resources low
138 // by avoiding some calculations and an O(log(N)) array access
139 if (2 == size())
140 {
141 // if same StopOffset use front color
142 if (fTools::equal(fMin, fMax))
143 return front().getStopColor();
144
145 const basegfx::BColor aCStart(front().getStopColor());
146 const basegfx::BColor aCEnd(back().getStopColor());
147
148 // if colors are equal just return one
149 if (aCStart == aCEnd)
150 return aCStart;
151
152 // calculate Steps
153 const sal_uInt32 nSteps(
154 basegfx::utils::calculateNumberOfSteps(nRequestedSteps, aCStart, aCEnd));
155
156 // we need to extend the interpolation to the local
157 // range of ColorStops. Despite having two ColorStops
158 // these are not necessarily at 0.0 and 1.0, so may be
159 // not the classical Start/EndColor (what is allowed)
160 fPosition = (fPosition - fMin) / (fMax - fMin);
161 return basegfx::interpolate(aCStart, aCEnd,
162 nSteps > 1 ? floor(fPosition * nSteps) / double(nSteps - 1)
163 : fPosition);
164 }
165
166 // check if we need to newly populate the needed interpolation data
167 // or if we can re-use from last time.
168 // If this scope is not entered, we do not need the binary search. It's
169 // only a single buffered entry, and only used when more than three
170 // ColorStops exist, but makes a huge difference compared with accessing
171 // the sorted ColorStop vector each time.
172 // NOTE: with this simple change I get very high hit rates, e.g. rotating
173 // a donut with gradient test '1' hit rate is at 0.99909440357755486
174 if (rLastColorStopRange.mfOffsetStart == rLastColorStopRange.mfOffsetEnd
175 || fPosition < rLastColorStopRange.mfOffsetStart
176 || fPosition > rLastColorStopRange.mfOffsetEnd)
177 {
178 // access needed spot in sorted array using binary search
179 // NOTE: This *seems* slow(er) when developing compared to just
180 // looping/accessing, but that's just due to the extensive
181 // debug test code created by the stl. In a pro version,
182 // all is good/fast as expected
183 const auto upperBound(std::upper_bound(begin(), end(), BColorStop(fPosition),
184 [](const BColorStop& x, const BColorStop& y) {
185 return x.getStopOffset() < y.getStopOffset();
186 }));
187
188 // no upper bound, done
189 if (end() == upperBound)
190 return back().getStopColor();
191
192 // lower bound is one entry back, access that
193 const auto lowerBound(upperBound - 1);
194
195 // no lower bound, done
196 if (end() == lowerBound)
197 return back().getStopColor();
198
199 // we have lower and upper bound, get colors and offsets
200 rLastColorStopRange.maColorStart = lowerBound->getStopColor();
201 rLastColorStopRange.maColorEnd = upperBound->getStopColor();
202 rLastColorStopRange.mfOffsetStart = lowerBound->getStopOffset();
203 rLastColorStopRange.mfOffsetEnd = upperBound->getStopOffset();
204 }
205
206 // when there are just two color steps this cannot happen, but when using
207 // a range of colors this *may* be used inside the range to represent
208 // single-colored regions inside a ColorRange. Use that color & done
209 if (rLastColorStopRange.maColorStart == rLastColorStopRange.maColorEnd)
210 return rLastColorStopRange.maColorStart;
211
212 // calculate number of steps and adapted proportional
213 // range for scaler in [0.0 .. 1.0]
214 const double fAdaptedScaler(
215 (fPosition - rLastColorStopRange.mfOffsetStart)
216 / (rLastColorStopRange.mfOffsetEnd - rLastColorStopRange.mfOffsetStart));
217 const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
218 nRequestedSteps, rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd));
219
220 // interpolate & evtl. apply steps
221 return interpolate(rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd,
222 nSteps > 1 ? floor(fAdaptedScaler * nSteps) / double(nSteps - 1)
223 : fAdaptedScaler);
224}
225
226/* Tooling method that allows to replace the StartColor in a
227 vector of ColorStops. A vector in 'ordered state' is expected,
228 so you may use/have used sortAndCorrect.
229 This method is for convenience & backwards compatibility, please
230 think about handling multi-colored gradients directly.
231 */
233{
234 BColorStops::iterator a1stNonStartColor(begin());
235
236 // search for highest existing non-StartColor - CAUTION,
237 // there might be none, one or multiple with StopOffset 0.0
238 while (a1stNonStartColor != end()
239 && basegfx::fTools::lessOrEqual(a1stNonStartColor->getStopOffset(), 0.0))
240 a1stNonStartColor++;
241
242 // create new ColorStops by 1st adding new one and then all
243 // non-StartColor entries
244 BColorStops aNewColorStops;
245
246 aNewColorStops.reserve(size() + 1);
247 aNewColorStops.emplace_back(0.0, rStart);
248 aNewColorStops.insert(aNewColorStops.end(), a1stNonStartColor, end());
249
250 // assign & done
251 *this = aNewColorStops;
252}
253
254/* Tooling method that allows to replace the EndColor in a
255 vector of ColorStops. A vector in 'ordered state' is expected,
256 so you may use/have used sortAndCorrectColorStops.
257 This method is for convenience & backwards compatibility, please
258 think about handling multi-colored gradients directly.
259 */
261{
262 // erase all evtl. existing EndColor(s)
263 while (!empty() && basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0))
264 pop_back();
265
266 // add at the end of existing ColorStops
267 emplace_back(1.0, rEnd);
268}
269
270/* Tooling method to linearly blend the Colors contained in
271 a given ColorStop vector against a given Color using the
272 given intensity values.
273 The intensity values fStartIntensity, fEndIntensity are
274 in the range of [0.0 .. 1.0] and describe how much the
275 blend is supposed to be done at the start color position
276 and the end color position respectively, where 0.0 means
277 to fully use the given BlendColor, 1.0 means to not change
278 the existing color in the ColorStop.
279 Every color entry in the given ColorStop is blended
280 relative to it's StopPosition, interpolating the
281 given intensities with the range [0.0 .. 1.0] to do so.
282 */
283void BColorStops::blendToIntensity(double fStartIntensity, double fEndIntensity,
284 const BColor& rBlendColor)
285{
286 // no entries, done
287 if (empty())
288 return;
289
290 // correct intensities (maybe assert when input was wrong)
291 fStartIntensity = std::max(std::min(1.0, fStartIntensity), 0.0);
292 fEndIntensity = std::max(std::min(1.0, fEndIntensity), 0.0);
293
294 // all 100%, no real blend, done
295 if (basegfx::fTools::equal(fStartIntensity, 1.0) && basegfx::fTools::equal(fEndIntensity, 1.0))
296 return;
297
298 // blend relative to StopOffset position
299 for (auto& candidate : *this)
300 {
301 const double fOffset(candidate.getStopOffset());
302 const double fIntensity((fStartIntensity * (1.0 - fOffset)) + (fEndIntensity * fOffset));
303 candidate = basegfx::BColorStop(
304 fOffset, basegfx::interpolate(rBlendColor, candidate.getStopColor(), fIntensity));
305 }
306}
307
308/* Tooling method to guarantee sort and correctness for
309 the given ColorStops vector.
310 A vector fulfilling these conditions is called to be
311 in 'ordered state'.
312
313 At return, the following conditions are guaranteed:
314 - contains no ColorStops with offset < 0.0 (will
315 be removed)
316 - contains no ColorStops with offset > 1.0 (will
317 be removed)
318 - ColorStops with identical offsets are now allowed
319 - will be sorted from lowest offset to highest
320
321 Some more notes:
322 - It can happen that the result is empty
323 - It is allowed to have consecutive entries with
324 the same color, this represents single-color
325 regions inside the gradient
326 - A entry with 0.0 is not required or forced, so
327 no 'StartColor' is technically required
328 - A entry with 1.0 is not required or forced, so
329 no 'EndColor' is technically required
330
331 All this is done in one run (sort + O(N)) without
332 creating a copy of the data in any form
333 */
335{
336 // no content, we are done
337 if (empty())
338 return;
339
340 if (1 == size())
341 {
342 // no gradient at all, but preserve given color
343 // evtl. correct offset to be in valid range [0.0 .. 1.0]
344 // NOTE: This does not move it to 0.0 or 1.0, it *can* still
345 // be somewhere in-between what is allowed
346 const BColorStop aEntry(front());
347 clear();
348 emplace_back(std::max(0.0, std::min(1.0, aEntry.getStopOffset())), aEntry.getStopColor());
349
350 // done
351 return;
352 }
353
354 // start with sorting the input data. Remember that
355 // this preserves the order of equal entries, where
356 // equal is defined here by offset (see use operator==)
357 std::sort(begin(), end());
358
359 // prepare status values
360 size_t write(0);
361
362 // use the paradigm of a band machine with two heads, read
363 // and write with write <= read all the time. Step over the
364 // data using read and check for valid entry. If valid, decide
365 // how to keep it
366 for (size_t read(0); read < size(); read++)
367 {
368 // get offset of entry at read position
369 double fOff((*this)[read].getStopOffset());
370
371 if (basegfx::fTools::less(fOff, 0.0) && read + 1 < size())
372 {
373 // value < 0.0 and we have a next entry. check for gradient snippet
374 // containing 0.0 resp. StartColor
375 const double fOff2((*this)[read + 1].getStopOffset());
376
377 if (basegfx::fTools::more(fOff2, 0.0))
378 {
379 // read is the start of a gradient snippet containing 0.0. Correct
380 // entry to StartColor, interpolate to correct StartColor
381 (*this)[read]
382 = BColorStop(0.0, basegfx::interpolate((*this)[read].getStopColor(),
383 (*this)[read + 1].getStopColor(),
384 (0.0 - fOff) / (fOff2 - fOff)));
385
386 // adapt fOff
387 fOff = 0.0;
388 }
389 }
390
391 // step over < 0 values, these are outside and will be removed
392 if (basegfx::fTools::less(fOff, 0.0))
393 {
394 continue;
395 }
396
397 if (basegfx::fTools::less(fOff, 1.0) && read + 1 < size())
398 {
399 // value < 1.0 and we have a next entry. check for gradient snippet
400 // containing 1.0 resp. EndColor
401 const double fOff2((*this)[read + 1].getStopOffset());
402
403 if (basegfx::fTools::more(fOff2, 1.0))
404 {
405 // read is the start of a gradient snippet containing 1.0. Correct
406 // next entry to EndColor, interpolate to correct EndColor
407 (*this)[read + 1]
408 = BColorStop(1.0, basegfx::interpolate((*this)[read].getStopColor(),
409 (*this)[read + 1].getStopColor(),
410 (1.0 - fOff) / (fOff2 - fOff)));
411
412 // adapt fOff
413 fOff = 1.0;
414 }
415 }
416
417 // step over > 1 values; even break, since all following
418 // entries will also be bigger due to being sorted, so done
419 if (basegfx::fTools::more(fOff, 1.0))
420 {
421 break;
422 }
423
424 // entry is valid value at read position
425 // copy if write target is empty (write at start) or when
426 // write target is different to read in color or offset
427 if (0 == write || !((*this)[read] == (*this)[write - 1]))
428 {
429 if (write != read)
430 {
431 // copy read to write backwards to close gaps
432 (*this)[write] = (*this)[read];
433 }
434
435 // always forward write position
436 write++;
437 }
438 }
439
440 // correct size when length is reduced. write is always at
441 // last used position + 1
442 if (size() > write)
443 {
444 if (0 == write)
445 {
446 // no valid entries at all, but not empty. This can only happen
447 // when all entries are below 0.0 or above 1.0 (else a gradient
448 // snippet spawning over both would have been detected)
449 if (basegfx::fTools::less(back().getStopOffset(), 0.0))
450 {
451 // all outside too low, rescue last due to being closest to content
452 const BColor aBackColor(back().getStopColor());
453 clear();
454 emplace_back(0.0, aBackColor);
455 }
456 else // if (basegfx::fTools::more(front().getStopOffset(), 1.0))
457 {
458 // all outside too high, rescue first due to being closest to content
459 const BColor aFrontColor(front().getStopColor());
460 clear();
461 emplace_back(1.0, aFrontColor);
462 }
463 }
464 else
465 {
466 resize(write);
467 }
468 }
469}
470
472{
473 // not needed when no ColorStops
474 if (empty())
475 return false;
476
477 // not needed when last ColorStop at the end or outside
478 if (basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0))
479 return false;
480
481 // get penultimate entry
482 const auto penultimate(rbegin() + 1);
483
484 // if there is none, we need no correction and are done
485 if (penultimate == rend())
486 return false;
487
488 // not needed when the last two ColorStops have different offset, then
489 // a visible range will be processed already
490 if (!basegfx::fTools::equal(back().getStopOffset(), penultimate->getStopOffset()))
491 return false;
492
493 // not needed when the last two ColorStops have the same Color, then the
494 // range before solves the problem
495 if (back().getStopColor() == penultimate->getStopColor())
496 return false;
497
498 return true;
499}
500
501/* Tooling method to fill a awt::ColorStopSequence with
502 the data from the given ColorStops. This is used in
503 UNO API implementations.
504 */
505css::awt::ColorStopSequence BColorStops::getAsColorStopSequence() const
506{
507 css::awt::ColorStopSequence aRetval(size());
508 // rColorStopSequence.realloc(rColorStops.size());
509 css::awt::ColorStop* pTargetColorStop(aRetval.getArray());
510
511 for (const auto& candidate : *this)
512 {
513 pTargetColorStop->StopOffset = candidate.getStopOffset();
514 pTargetColorStop->StopColor = css::rendering::RGBColor(candidate.getStopColor().getRed(),
515 candidate.getStopColor().getGreen(),
516 candidate.getStopColor().getBlue());
517 pTargetColorStop++;
518 }
519
520 return aRetval;
521}
522
523/* Tooling method to check if a ColorStop vector is defined
524 by a single color. It returns true if this is the case.
525 If true is returned, rSingleColor contains that single
526 color for convenience.
527 NOTE: If no ColorStop is defined, a fallback to BColor-default
528 (which is black) and true will be returned
529 */
530bool BColorStops::isSingleColor(BColor& rSingleColor) const
531{
532 if (empty())
533 {
534 rSingleColor = BColor();
535 return true;
536 }
537
538 if (1 == size())
539 {
540 rSingleColor = front().getStopColor();
541 return true;
542 }
543
544 rSingleColor = front().getStopColor();
545
546 for (auto const& rCandidate : *this)
547 {
548 if (rCandidate.getStopColor() != rSingleColor)
549 return false;
550 }
551
552 return true;
553}
554
555/* Tooling method to reverse ColorStops, including offsets.
556 When also mirroring offsets a valid sort keeps valid.
557 */
559{
560 // can use std::reverse, but also need to adapt offset(s)
561 std::reverse(begin(), end());
562 for (auto& candidate : *this)
563 candidate = BColorStop(1.0 - candidate.getStopOffset(), candidate.getStopColor());
564}
565
566// createSpaceAtStart creates fOffset space at start by
567// translating/scaling all entries to the right
569{
570 // nothing to do if empty
571 if (empty())
572 return;
573
574 // correct offset to [0.0 .. 1.0]
575 fOffset = std::max(std::min(1.0, fOffset), 0.0);
576
577 // nothing to do if 0.0 == offset
578 if (basegfx::fTools::equalZero(fOffset))
579 return;
580
581 BColorStops aNewStops;
582
583 for (const auto& candidate : *this)
584 {
585 aNewStops.emplace_back(fOffset + (candidate.getStopOffset() * (1.0 - fOffset)),
586 candidate.getStopColor());
587 }
588
589 *this = aNewStops;
590}
591
592// removeSpaceAtStart removes fOffset space from start by
593// translating/scaling entries more or equal to fOffset
594// to the left. Entries less than fOffset will be removed
596{
597 // nothing to do if empty
598 if (empty())
599 return;
600
601 // correct factor to [0.0 .. 1.0]
602 fOffset = std::max(std::min(1.0, fOffset), 0.0);
603
604 // nothing to do if fOffset == 0.0
605 if (basegfx::fTools::equalZero(fOffset))
606 return;
607
608 BColorStops aNewStops;
609 const double fMul(basegfx::fTools::equal(fOffset, 1.0) ? 1.0 : 1.0 / (1.0 - fOffset));
610
611 for (const auto& candidate : *this)
612 {
613 if (basegfx::fTools::moreOrEqual(candidate.getStopOffset(), fOffset))
614 {
615 aNewStops.emplace_back((candidate.getStopOffset() - fOffset) * fMul,
616 candidate.getStopColor());
617 }
618 }
619
620 *this = aNewStops;
621}
622
623// try to detect if an empty/no-color-change area exists
624// at the start and return offset to it. Returns 0.0 if not.
626{
627 BColor aSingleColor;
628 const bool bSingleColor(isSingleColor(aSingleColor));
629
630 // no useful offset for single color
631 if (bSingleColor)
632 return 0.0;
633
634 // here we know that we have at least two colors, so we have a
635 // color change. Find colors left and right of that first color change
636 BColorStops::const_iterator aColorR(begin());
637 BColorStops::const_iterator aColorL(aColorR++);
638
639 // aColorR would 1st get equal to end(), so no need to also check aColorL
640 // for end(). Loop as long as same color. Since we *have* a color change
641 // not even aColorR can get equal to end() before color inequality, but
642 // keep for safety
643 while (aColorR != end() && aColorL->getStopColor() == aColorR->getStopColor())
644 {
645 aColorL++;
646 aColorR++;
647 }
648
649 // also for safety: access values at aColorL below *only*
650 // if not equal to end(), but can theoretically not happen
651 if (aColorL == end())
652 {
653 return 0.0;
654 }
655
656 // return offset (maybe 0.0 what is OK)
657 return aColorL->getStopOffset();
658}
659
660// checks whether the color stops are symmetrical in color and offset.
662{
663 if (empty())
664 return false;
665 if (1 == size())
666 return basegfx::fTools::equal(0.5, front().getStopOffset());
667
668 BColorStops::const_iterator aIter(begin()); // for going forward
669 BColorStops::const_iterator aRIter(end()); // for going backward
670 --aRIter;
671 // We have at least two elements, so aIter <= aRIter fails before iterators no longer point to
672 // an element.
673 while (aIter <= aRIter && aIter->getStopColor().equal(aRIter->getStopColor())
674 && basegfx::fTools::equal(aIter->getStopOffset(), 1.0 - aRIter->getStopOffset()))
675 {
676 ++aIter;
677 --aRIter;
678 }
679 return aIter > aRIter;
680}
681
683{
684 // prepare new ColorStops
685 basegfx::BColorStops aNewColorStops;
686
687 // add gradient stops in reverse order, scaled to [0.0 .. 0.5]
688 basegfx::BColorStops::const_reverse_iterator aRevCurrColor(rbegin());
689
690 while (aRevCurrColor != rend())
691 {
692 aNewColorStops.emplace_back((1.0 - aRevCurrColor->getStopOffset()) * 0.5,
693 aRevCurrColor->getStopColor());
694 aRevCurrColor++;
695 }
696
697 // prepare non-reverse run
698 basegfx::BColorStops::const_iterator aCurrColor(begin());
699
700 if (basegfx::fTools::equalZero(aCurrColor->getStopOffset()))
701 {
702 // Caution: do not add 1st entry again, that would be double since it was
703 // already added as last element of the inverse run above. But only if
704 // the gradient has a start entry for 0.0 aka StartColor, else it is correct.
705 aCurrColor++;
706 }
707
708 // add gradient stops in non-reverse order, translated and scaled to [0.5 .. 1.0]
709 while (aCurrColor != end())
710 {
711 aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 0.5) + 0.5,
712 aCurrColor->getStopColor());
713 aCurrColor++;
714 }
715
716 // apply color stops
717 *this = aNewColorStops;
718}
719
720void BColorStops::doApplySteps(sal_uInt16 nStepCount)
721{
722 // check for zero or invalid steps setting -> done
723 if (0 == nStepCount || nStepCount > 100)
724 return;
725
726 // no change needed if single color
727 BColor aSingleColor;
728 if (isSingleColor(aSingleColor))
729 return;
730
731 // prepare new color stops, get L/R iterators for segments
732 basegfx::BColorStops aNewColorStops;
733 basegfx::BColorStops::const_iterator aColorR(begin());
734 basegfx::BColorStops::const_iterator aColorL(aColorR++);
735
736 while (aColorR != end())
737 {
738 // get start/end color for segment
739 const double fStart(aColorL->getStopOffset());
740 const double fDelta(aColorR->getStopOffset() - fStart);
741
742 if (aNewColorStops.empty() || aNewColorStops.back() != *aColorL)
743 {
744 // add start color, but check if it is already there - which is the
745 // case from the 2nd segment on due to a new segment starting with
746 // the same color as the previous one ended
747 aNewColorStops.push_back(*aColorL);
748 }
749 if (!basegfx::fTools::equalZero(fDelta))
750 {
751 // create in-between steps, always two at the same position to
752 // define a 'hard' color stop. Get start/end color for the segment
753 const basegfx::BColor& rStartColor(aColorL->getStopColor());
754 const basegfx::BColor& rEndColor(aColorR->getStopColor());
755
756 if (rStartColor != rEndColor)
757 {
758 // get relative single-step width
759 // tdf155852 Use same method for the color as in rendering.
760 const double fSingleStep(1.0 / static_cast<double>(nStepCount - 1));
761 const double fOffsetStep(fDelta / static_cast<double>(nStepCount));
762
763 for (sal_uInt16 a(1); a < nStepCount; a++)
764 {
765 // calculate stop position since being used twice
766 const double fPosition(fStart + fOffsetStep * static_cast<double>(a));
767
768 // add end color of previous sub-segment
769 aNewColorStops.emplace_back(
770 fPosition, basegfx::interpolate(rStartColor, rEndColor,
771 static_cast<double>(a - 1) * fSingleStep));
772
773 // add start color of current sub-segment
774 aNewColorStops.emplace_back(
775 fPosition, basegfx::interpolate(rStartColor, rEndColor,
776 static_cast<double>(a) * fSingleStep));
777 }
778 }
779 }
780
781 // always add end color of segment
782 aNewColorStops.push_back(*aColorR);
783
784 // next segment
785 aColorL++;
786 aColorR++;
787 }
788
789 // apply the change to color stops
790 *this = aNewColorStops;
791}
792
793std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle)
794{
795 switch (eStyle)
796 {
797 case css::awt::GradientStyle::GradientStyle_LINEAR:
798 return "LINEAR";
799
800 case css::awt::GradientStyle::GradientStyle_AXIAL:
801 return "AXIAL";
802
803 case css::awt::GradientStyle::GradientStyle_RADIAL:
804 return "RADIAL";
805
806 case css::awt::GradientStyle::GradientStyle_ELLIPTICAL:
807 return "ELLIPTICAL";
808
809 case css::awt::GradientStyle::GradientStyle_SQUARE:
810 return "SQUARE";
811
812 case css::awt::GradientStyle::GradientStyle_RECT:
813 return "RECT";
814
815 case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE:
816 return "MAKE_FIXED_SIZE";
817 }
818
819 return "";
820}
821
822BGradient BGradient::fromJSON(std::u16string_view rJSON)
823{
824 StringMap aMap(lcl_jsonToStringMap(rJSON));
825 return lcl_buildGradientFromStringMap(aMap);
826}
827
829 : eStyle(css::awt::GradientStyle_LINEAR)
830 , aColorStops()
831 , nAngle(0)
832 , nBorder(0)
833 , nOfsX(50)
834 , nOfsY(50)
835 , nIntensStart(100)
836 , nIntensEnd(100)
837 , nStepCount(0)
838{
839 aColorStops.emplace_back(0.0, BColor(0.0, 0.0, 0.0)); // COL_BLACK
840 aColorStops.emplace_back(1.0, BColor(1.0, 1.0, 1.0)); // COL_WHITE
841}
842
843BGradient::BGradient(const basegfx::BColorStops& rColorStops, css::awt::GradientStyle eTheStyle,
844 Degree10 nTheAngle, sal_uInt16 nXOfs, sal_uInt16 nYOfs, sal_uInt16 nTheBorder,
845 sal_uInt16 nStartIntens, sal_uInt16 nEndIntens, sal_uInt16 nSteps)
846 : eStyle(eTheStyle)
847 , aColorStops(rColorStops)
848 , nAngle(nTheAngle)
849 , nBorder(nTheBorder)
850 , nOfsX(nXOfs)
851 , nOfsY(nYOfs)
852 , nIntensStart(nStartIntens)
853 , nIntensEnd(nEndIntens)
854 , nStepCount(nSteps)
855{
857}
858
859void BGradient::setGradient2(const css::awt::Gradient2& rGradient2)
860{
861 // set values
862 SetGradientStyle(rGradient2.Style);
863 SetAngle(Degree10(rGradient2.Angle));
864 SetBorder(rGradient2.Border);
865 SetXOffset(rGradient2.XOffset);
866 SetYOffset(rGradient2.YOffset);
867 SetStartIntens(rGradient2.StartIntensity);
868 SetEndIntens(rGradient2.EndIntensity);
869 SetSteps(rGradient2.StepCount);
870
871 // set ColorStops
872 if (rGradient2.ColorStops.hasElements())
873 {
874 // if we have a awt::ColorStopSequence, use it
875 aColorStops = BColorStops(rGradient2.ColorStops);
877 }
878 else
879 {
880 // if not, for compatibility, use StartColor/EndColor
882 BColorStop(0.0, ColorToBColorConverter(rGradient2.StartColor).getBColor()),
883 BColorStop(1.0, ColorToBColorConverter(rGradient2.EndColor).getBColor())
884 };
885 }
886}
887
888BGradient::BGradient(const css::awt::Gradient2& rGradient2) { setGradient2(rGradient2); }
889
890BGradient::BGradient(const css::uno::Any& rVal)
891 : BGradient()
892{
893 if (rVal.has<css::awt::Gradient2>())
894 {
895 // we can use awt::Gradient2 directly
896 css::awt::Gradient2 aGradient2;
897 rVal >>= aGradient2;
898
899 setGradient2(aGradient2);
900 }
901 else if (rVal.has<css::awt::Gradient>())
902 {
903 // use awt::Gradient
904 css::awt::Gradient aGradient;
905 rVal >>= aGradient;
906
907 // set values
908 SetGradientStyle(aGradient.Style);
909 SetAngle(Degree10(aGradient.Angle));
910 SetBorder(aGradient.Border);
911 SetXOffset(aGradient.XOffset);
912 SetYOffset(aGradient.YOffset);
913 SetStartIntens(aGradient.StartIntensity);
914 SetEndIntens(aGradient.EndIntensity);
915 SetSteps(aGradient.StepCount);
916
917 // complete data by creating ColorStops from fixed Start/EndColor
919 BColorStop(0.0, ColorToBColorConverter(aGradient.StartColor).getBColor()),
920 BColorStop(1.0, ColorToBColorConverter(aGradient.EndColor).getBColor())
921 };
922 }
923}
924
925bool BGradient::operator==(const BGradient& rGradient) const
926{
927 return (eStyle == rGradient.eStyle && aColorStops == rGradient.aColorStops
928 && nAngle == rGradient.nAngle && nBorder == rGradient.nBorder
929 && nOfsX == rGradient.nOfsX && nOfsY == rGradient.nOfsY
930 && nIntensStart == rGradient.nIntensStart && nIntensEnd == rGradient.nIntensEnd
931 && nStepCount == rGradient.nStepCount);
932}
933
935{
936 aColorStops = rSteps;
938 if (aColorStops.empty())
939 aColorStops.emplace_back(0.0, basegfx::BColor());
940}
941
942namespace
943{
944OUString AsRGBHexString(const ColorToBColorConverter& rVal)
945{
946 std::stringstream ss;
947 ss << std::hex << std::setfill('0') << std::setw(6) << sal_uInt32(rVal);
948 return OUString::createFromAscii(ss.str());
949}
950}
951
952boost::property_tree::ptree BGradient::dumpAsJSON() const
953{
954 boost::property_tree::ptree aTree;
955
956 aTree.put("style", BGradient::GradientStyleToString(eStyle));
957 const ColorToBColorConverter aStart(GetColorStops().front().getStopColor());
958 aTree.put("startcolor", AsRGBHexString(aStart.GetRGBColor()));
959 const ColorToBColorConverter aEnd(GetColorStops().back().getStopColor());
960 aTree.put("endcolor", AsRGBHexString(aEnd.GetRGBColor()));
961 aTree.put("angle", std::to_string(nAngle.get()));
962 aTree.put("border", std::to_string(nBorder));
963 aTree.put("x", std::to_string(nOfsX));
964 aTree.put("y", std::to_string(nOfsY));
965 aTree.put("intensstart", std::to_string(nIntensStart));
966 aTree.put("intensend", std::to_string(nIntensEnd));
967 aTree.put("stepcount", std::to_string(nStepCount));
968
969 return aTree;
970}
971
972css::awt::Gradient2 BGradient::getAsGradient2() const
973{
974 css::awt::Gradient2 aRetval;
975
976 // standard values
977 aRetval.Style = GetGradientStyle();
978 aRetval.Angle = static_cast<short>(GetAngle());
979 aRetval.Border = GetBorder();
980 aRetval.XOffset = GetXOffset();
981 aRetval.YOffset = GetYOffset();
982 aRetval.StartIntensity = GetStartIntens();
983 aRetval.EndIntensity = GetEndIntens();
984 aRetval.StepCount = GetSteps();
985
986 // for compatibility, still set StartColor/EndColor
987 // NOTE: All code after adapting to multi color gradients works
988 // using the ColorSteps, so in principle Start/EndColor might
989 // be either
990 // (a) ignored consequently everywhere or
991 // (b) be set/added consequently everywhere
992 // since this is - in principle - redundant data.
993 // Be aware that e.g. cases like DrawingML::EqualGradients
994 // and others would have to be identified and adapted (!)
995 // Since awt::Gradient2 is UNO API data there might
996 // be cases where just awt::Gradient is transferred, so (b)
997 // is far better backwards compatible and thus more safe, so
998 // all changes will make use of additionally using/setting
999 // these additionally, but will only make use of the given
1000 // ColorSteps if these are not empty, assuming that these
1001 // already contain Start/EndColor.
1002 // In principle that redundancy and that it is conflict-free
1003 // could even be checked and asserted, but consequently using
1004 // (b) methodically should be safe.
1005 aRetval.StartColor
1006 = static_cast<sal_Int32>(ColorToBColorConverter(aColorStops.front().getStopColor()));
1007 aRetval.EndColor
1008 = static_cast<sal_Int32>(ColorToBColorConverter(aColorStops.back().getStopColor()));
1009
1010 // fill ColorStops to extended Gradient2
1011 aRetval.ColorStops = aColorStops.getAsColorStopSequence();
1012
1013 return aRetval;
1014}
1015
1016void BGradient::tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops)
1017{
1018 // border already set, do not try to recreate
1019 if (0 != GetBorder())
1020 return;
1021
1022 BColor aSingleColor;
1023 const bool bSingleColor(GetColorStops().isSingleColor(aSingleColor));
1024
1025 // no need to recreate with single color
1026 if (bSingleColor)
1027 return;
1028
1029 const bool bIsAxial(css::awt::GradientStyle_AXIAL == GetGradientStyle());
1030
1031 if (bIsAxial)
1032 {
1033 // for axial due to reverse used gradient work reversed
1035 if (nullptr != pAssociatedTransparencyStops)
1036 pAssociatedTransparencyStops->reverseColorStops();
1037 }
1038
1039 // check if we have space at start of range [0.0 .. 1.0] that
1040 // may be interpreted as 'border' -> same color. That may involve
1041 // different scenarios, e.g. 1st index > 0.0, but also a non-zero
1042 // number of same color entries, or a combination of both
1043 const double fOffset(aColorStops.detectPossibleOffsetAtStart());
1044
1045 if (!basegfx::fTools::equalZero(fOffset))
1046 {
1047 // we have a border area, indeed re-create
1049 if (nullptr != pAssociatedTransparencyStops)
1050 pAssociatedTransparencyStops->removeSpaceAtStart(fOffset);
1051
1052 // ...and create border value
1053 SetBorder(static_cast<sal_uInt16>(std::lround(fOffset * 100.0)));
1054 }
1055
1056 if (bIsAxial)
1057 {
1058 // take back reverse
1060 if (nullptr != pAssociatedTransparencyStops)
1061 pAssociatedTransparencyStops->reverseColorStops();
1062 }
1063}
1064
1066{
1067 // no border to apply, done
1068 if (0 == GetBorder())
1069 return;
1070
1071 // NOTE: no new start node is added. The new ColorStop
1072 // mechanism does not need entries at 0.0 and 1.0.
1073 // In case this is needed, do that in the caller
1074 const double fOffset(GetBorder() * 0.01);
1075
1076 if (css::awt::GradientStyle_AXIAL == GetGradientStyle())
1077 {
1078 // for axial due to reverse used gradient work reversed
1082 }
1083 else
1084 {
1085 // apply border to GradientSteps
1087 }
1088
1089 // set changed values
1090 SetBorder(0);
1091}
1092
1094{
1095 // already on default, nothing to apply
1096 if (100 == GetStartIntens() && 100 == GetEndIntens())
1097 return;
1098
1099 // apply 'old' blend stuff, blend against black
1101 BColor()); // COL_BLACK
1102
1103 // set values to default
1104 SetStartIntens(100);
1105 SetEndIntens(100);
1106}
1107
1109{
1110 if (css::awt::GradientStyle_LINEAR != GetGradientStyle() || 0 != GetBorder()
1111 || GetColorStops().empty())
1112 return;
1113
1114 if (!GetColorStops().isSymmetrical())
1115 return;
1116
1117 SetGradientStyle(css::awt::GradientStyle_AXIAL);
1118
1119 // Stretch the first half of the color stops to double width
1120 // and collect them in a new color stops vector.
1121 BColorStops aAxialColorStops;
1122 aAxialColorStops.reserve(std::ceil(GetColorStops().size() / 2.0));
1123 BColorStops::const_iterator aIter(GetColorStops().begin());
1124 while (basegfx::fTools::lessOrEqual(aIter->getStopOffset(), 0.5))
1125 {
1126 BColorStop aNextStop(std::clamp((*aIter).getStopOffset() * 2.0, 0.0, 1.0),
1127 (*aIter).getStopColor());
1128 aAxialColorStops.push_back(aNextStop);
1129 ++aIter;
1130 }
1131 // Axial gradients have outmost color as last color stop.
1132 aAxialColorStops.reverseColorStops();
1133
1134 SetColorStops(aAxialColorStops);
1135}
1136
1138{
1139 // only need to do something if css::awt::GradientStyle_AXIAL, else done
1140 if (GetGradientStyle() != css::awt::GradientStyle_AXIAL)
1141 return;
1142
1143 // apply the change to color stops
1145
1146 // set style to GradientStyle_LINEAR
1147 SetGradientStyle(css::awt::GradientStyle_LINEAR);
1148}
1149
1151{
1152 // check for zero or invalid steps setting -> done
1153 if (0 == GetSteps() || GetSteps() > 100)
1154 return;
1155
1156 // do the action
1158
1159 // set value to default
1160 SetSteps(0);
1161}
1162}
1163
1164/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::map< OUString, OUString > StringMap
Definition: bgradient.cxx:16
const BColor & getStopColor() const
Definition: bgradient.hxx:86
double getStopOffset() const
Definition: bgradient.hxx:85
void setColorStopSequence(const css::awt::ColorStopSequence &rColorStops)
Definition: bgradient.cxx:71
void blendToIntensity(double fStartIntensity, double fEndIntensity, const BColor &rBlendColor)
Definition: bgradient.cxx:283
bool isSingleColor(BColor &rSingleColor) const
Definition: bgradient.cxx:530
void replaceEndColor(const BColor &rEnd)
Definition: bgradient.cxx:260
void removeSpaceAtStart(double fOffset)
Definition: bgradient.cxx:595
bool checkPenultimate() const
Definition: bgradient.cxx:471
BColor getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps, BColorStopRange &rLastColorStopRange) const
Definition: bgradient.cxx:119
css::awt::ColorStopSequence getAsColorStopSequence() const
Definition: bgradient.cxx:505
void createSpaceAtStart(double fOffset)
Definition: bgradient.cxx:568
bool isSymmetrical() const
Definition: bgradient.cxx:661
double detectPossibleOffsetAtStart() const
Definition: bgradient.cxx:625
void replaceStartColor(const BColor &rStart)
Definition: bgradient.cxx:232
void doApplySteps(sal_uInt16 nStepCount)
Definition: bgradient.cxx:720
Base Color class with three double values.
Definition: bcolor.hxx:41
bool operator==(const BGradient &rGradient) const
Definition: bgradient.cxx:925
sal_uInt16 nStepCount
Definition: bgradient.hxx:304
void tryToRecreateBorder(basegfx::BColorStops *pAssociatedTransparencyStops=nullptr)
Definition: bgradient.cxx:1016
sal_uInt16 GetBorder() const
Definition: bgradient.hxx:336
void SetGradientStyle(css::awt::GradientStyle eNewStyle)
Definition: bgradient.hxx:323
sal_uInt16 GetStartIntens() const
Definition: bgradient.hxx:339
sal_uInt16 nIntensStart
Definition: bgradient.hxx:302
void SetColorStops(const basegfx::BColorStops &rSteps)
Definition: bgradient.cxx:934
sal_uInt16 GetSteps() const
Definition: bgradient.hxx:341
void SetEndIntens(sal_uInt16 nNewIntens)
Definition: bgradient.hxx:330
void SetYOffset(sal_uInt16 nNewOffset)
Definition: bgradient.hxx:328
void SetXOffset(sal_uInt16 nNewOffset)
Definition: bgradient.hxx:327
basegfx::BColorStops aColorStops
Definition: bgradient.hxx:296
sal_uInt16 GetXOffset() const
Definition: bgradient.hxx:337
sal_uInt16 nIntensEnd
Definition: bgradient.hxx:303
void tryToApplyStartEndIntensity()
Definition: bgradient.cxx:1093
void SetSteps(sal_uInt16 nSteps)
Definition: bgradient.hxx:331
void SetBorder(sal_uInt16 nNewBorder)
Definition: bgradient.hxx:326
const basegfx::BColorStops & GetColorStops() const
Definition: bgradient.hxx:334
static std::string GradientStyleToString(css::awt::GradientStyle eStyle)
Definition: bgradient.cxx:793
css::awt::Gradient2 getAsGradient2() const
Tooling method to fill awt::Gradient2 from data contained in the given basegfx::BGradient.
Definition: bgradient.cxx:972
sal_uInt16 GetEndIntens() const
Definition: bgradient.hxx:340
void setGradient2(const css::awt::Gradient2 &rGradient2)
Definition: bgradient.cxx:859
void tryToConvertToAxial()
Definition: bgradient.cxx:1108
Degree10 GetAngle() const
Definition: bgradient.hxx:335
sal_uInt16 nOfsY
Definition: bgradient.hxx:301
static BGradient fromJSON(std::u16string_view rJSON)
Definition: bgradient.cxx:822
css::awt::GradientStyle eStyle
Definition: bgradient.hxx:293
css::awt::GradientStyle GetGradientStyle() const
Definition: bgradient.hxx:333
sal_uInt16 nBorder
Definition: bgradient.hxx:299
void SetStartIntens(sal_uInt16 nNewIntens)
Definition: bgradient.hxx:329
boost::property_tree::ptree dumpAsJSON() const
Definition: bgradient.cxx:952
void SetAngle(Degree10 nNewAngle)
Definition: bgradient.hxx:325
sal_uInt16 GetYOffset() const
Definition: bgradient.hxx:338
sal_uInt16 nOfsX
Definition: bgradient.hxx:300
float u
float y
float x
uno_Any a
tools::Long const nBorder
bool lessOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:188
bool more(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:194
bool equalZero(const T &rfVal)
Compare against small value.
Definition: ftools.hxx:156
bool equal(T const &rfValA, T const &rfValB)
Definition: ftools.hxx:169
bool less(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:182
bool moreOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:200
sal_uInt32 calculateNumberOfSteps(sal_uInt32 nRequestedSteps, const BColor &rStart, const BColor &rEnd)
B2DTuple interpolate(const B2DTuple &rOld1, const B2DTuple &rOld2, double t)
Definition: b2dtuple.hxx:96
size
enumrange< T >::Iterator begin(enumrange< T >)
end
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
bool equal(Pair const &p1, Pair const &p2)
sal_Int32 toInt32(std::u16string_view rStr)
HashMap_OWString_Interface aMap
UNDERLYING_TYPE get() const
std::map< OUString, OUString > StringMap