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