/src/libreoffice/include/basegfx/utils/bgradient.hxx
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 | | #pragma once |
11 | | |
12 | | #include <basegfx/color/bcolor.hxx> |
13 | | #include <basegfx/basegfxdllapi.h> |
14 | | #include <vector> |
15 | | #include <com/sun/star/awt/GradientStyle.hpp> |
16 | | #include <tools/degree.hxx> |
17 | | #include <boost/property_tree/ptree_fwd.hpp> |
18 | | |
19 | | namespace basegfx |
20 | | { |
21 | | class BColorModifierStack; |
22 | | } |
23 | | |
24 | | namespace basegfx |
25 | | { |
26 | | /* MCGR: Provide ColorStop definition |
27 | | |
28 | | This is the needed combination of offset and color: |
29 | | |
30 | | Offset is defined as: |
31 | | - being in the range of [0.0 .. 1.0] (unit range) |
32 | | - offsets outside are an error |
33 | | - lowest/1st value equivalent to StartColor |
34 | | - highest/last value equivalent to EndColor |
35 | | - missing 0.0/1.0 entries are allowed |
36 | | - at least one value (usually 0.0, StartColor) is required |
37 | | - this allows to avoid massive testing in all places where |
38 | | this data has to be accessed |
39 | | |
40 | | Color is defined as: |
41 | | - RGB with unit values [0.0 .. 1.0] |
42 | | |
43 | | These definitions are packed in a std::vector<ColorStop> ColorStops, |
44 | | see typedef below. |
45 | | */ |
46 | | class BASEGFX_DLLPUBLIC BColorStop |
47 | | { |
48 | | private: |
49 | | // offset in the range of [0.0 .. 1.0] |
50 | | double mfStopOffset; |
51 | | |
52 | | // RGB color of ColorStop entry |
53 | | BColor maStopColor; |
54 | | |
55 | | public: |
56 | | // constructor - defaults are needed to have a default constructor |
57 | | // e.g. for usage in std::vector::insert (even when only reducing) |
58 | | // ensure [0.0 .. 1.0] range for mfStopOffset |
59 | | BColorStop(double fStopOffset = 0.0, const BColor& rStopColor = BColor()) |
60 | 704k | : mfStopOffset(fStopOffset) |
61 | 704k | , maStopColor(rStopColor) |
62 | 704k | { |
63 | | // NOTE: I originally *corrected* mfStopOffset here by using |
64 | | // mfStopOffset(std::max(0.0, std::min(fOffset, 1.0))) |
65 | | // While that is formally correct, it moves an invalid |
66 | | // entry to 0.0 or 1.0, thus creating additional wrong |
67 | | // Start/EndColor entries. That may then 'overlay' the |
68 | | // correct entry when corrections are applied to the |
69 | | // vector of entries (see sortAndCorrectColorStops) |
70 | | // which leads to getting the wanted Start/EndColor |
71 | | // to be factically deleted, what is an error. |
72 | 704k | } |
73 | | |
74 | 1.70M | double getStopOffset() const { return mfStopOffset; } |
75 | 176k | const BColor& getStopColor() const { return maStopColor; } |
76 | | |
77 | | // needed for std::sort |
78 | | bool operator<(const BColorStop& rCandidate) const |
79 | 220k | { |
80 | 220k | return getStopOffset() < rCandidate.getStopOffset(); |
81 | 220k | } |
82 | | |
83 | | bool operator==(const BColorStop& rCandidate) const |
84 | 298k | { |
85 | 298k | return getStopOffset() == rCandidate.getStopOffset() |
86 | 78.1k | && getStopColor() == rCandidate.getStopColor(); |
87 | 298k | } |
88 | | |
89 | 0 | bool operator!=(const BColorStop& rCandidate) const { return !(*this == rCandidate); } |
90 | | }; |
91 | | |
92 | | /* MCGR: Provide ColorStops definition to the FillGradientAttribute |
93 | | |
94 | | This array should be sorted ascending by offsets, from lowest to |
95 | | highest. Since all the primitive data definition where it is used |
96 | | is read-only, this can/will be guaranteed by forcing/checking this |
97 | | in the constructor, see ::FillGradientAttribute |
98 | | */ |
99 | | class BASEGFX_DLLPUBLIC BColorStops final |
100 | | { |
101 | | private: |
102 | | std::vector<BColorStop> maStops; |
103 | | |
104 | | public: |
105 | | typedef typename std::vector<BColorStop>::size_type size_type; |
106 | | typedef typename std::vector<BColorStop>::const_iterator const_iterator; |
107 | | typedef typename std::vector<BColorStop>::const_reverse_iterator const_reverse_iterator; |
108 | | |
109 | 75.3k | explicit BColorStops() {} |
110 | | |
111 | | BColorStops(const BColorStops& rOther) |
112 | 314k | : maStops(rOther.maStops) |
113 | 314k | { |
114 | 314k | } |
115 | | BColorStops(BColorStops&& rOther) noexcept |
116 | 2.89k | : maStops(std::move(rOther.maStops)) |
117 | 2.89k | { |
118 | 2.89k | } |
119 | | BColorStops(std::initializer_list<BColorStop> init) |
120 | 716 | : maStops(init) |
121 | 716 | { |
122 | 716 | } |
123 | | |
124 | | // constructor with two colors to explicitly create a |
125 | | // BColorStops for StartColor @0.0 & EndColor @1.0 |
126 | | BColorStops(BColor const& rStart, BColor const& rEnd); |
127 | | |
128 | | BColorStops& operator=(const BColorStops& rOther) |
129 | 394k | { |
130 | 394k | maStops = rOther.maStops; |
131 | 394k | return *this; |
132 | 394k | } |
133 | | BColorStops& operator=(BColorStops&& rOther) noexcept |
134 | 0 | { |
135 | 0 | maStops = std::move(rOther.maStops); |
136 | 0 | return *this; |
137 | 0 | } |
138 | | |
139 | 702k | void addStop(double fOffset, BColor aColor) { maStops.emplace_back(fOffset, aColor); } |
140 | | |
141 | 18 | void addStop(BColorStop const& aColorStop) { maStops.push_back(aColorStop); } |
142 | | |
143 | 0 | BColorStop const& getStop(size_t i) const { return maStops[i]; } |
144 | 0 | double getStopOffset(size_t i) const { return maStops[i].getStopOffset(); } |
145 | | |
146 | | void changeStartAndEnd(BColor const& rStart, BColor const& rEnd); |
147 | | |
148 | 0 | BColor getStopColor(size_t i) const { return maStops[i].getStopColor(); } |
149 | | |
150 | 0 | void removeLastStop() { return maStops.pop_back(); } |
151 | | |
152 | 3.64k | void reserve(size_t nNumber) { maStops.reserve(nNumber); } |
153 | 3.64k | void clear() { maStops.clear(); } |
154 | 445k | bool empty() const { return maStops.empty(); } |
155 | 1.32M | size_type size() const { return maStops.size(); } |
156 | 5.89k | const_iterator begin() const { return maStops.begin(); } |
157 | 5.92k | const_iterator end() const { return maStops.end(); } |
158 | 0 | const_reverse_iterator rbegin() const { return maStops.rbegin(); } |
159 | 0 | const_reverse_iterator rend() const { return maStops.rend(); } |
160 | 4.13k | const BColorStop& front() const { return maStops.front(); } |
161 | 4.05k | const BColorStop& back() const { return maStops.back(); } |
162 | 49.9k | bool operator==(BColorStops const& rOther) const { return maStops == rOther.maStops; } |
163 | | |
164 | | // helper data struct to support buffering entries in |
165 | | // gradient texture mapping, see usages for more info |
166 | | struct BColorStopRange |
167 | | { |
168 | | basegfx::BColor maColorStart; |
169 | | basegfx::BColor maColorEnd; |
170 | | double mfOffsetStart; |
171 | | double mfOffsetEnd; |
172 | | |
173 | | BColorStopRange() |
174 | | : maColorStart() |
175 | | , maColorEnd() |
176 | | , mfOffsetStart(0.0) |
177 | | , mfOffsetEnd(0.0) |
178 | 0 | { |
179 | 0 | } |
180 | | }; |
181 | | |
182 | | /* Helper to grep the correct ColorStop out of |
183 | | ColorStops and interpolate as needed for given |
184 | | relative value in fPosition in the range of [0.0 .. 1.0]. |
185 | | It also takes care of evtl. given RequestedSteps. |
186 | | */ |
187 | | BColor getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps, |
188 | | BColorStopRange& rLastColorStopRange) const; |
189 | | |
190 | | /* Tooling method that allows to replace the StartColor in a |
191 | | vector of ColorStops. A vector in 'ordered state' is expected, |
192 | | so you may use/have used sortAndCorrect. |
193 | | This method is for convenience & backwards compatibility, please |
194 | | think about handling multi-colored gradients directly. |
195 | | */ |
196 | | void replaceStartColor(const BColor& rStart); |
197 | | |
198 | | /* Tooling method that allows to replace the EndColor in a |
199 | | vector of ColorStops. A vector in 'ordered state' is expected, |
200 | | so you may use/have used sortAndCorrect. |
201 | | This method is for convenience & backwards compatibility, please |
202 | | think about handling multi-colored gradients directly. |
203 | | */ |
204 | | void replaceEndColor(const BColor& rEnd); |
205 | | |
206 | | /* Tooling method to linearly blend the Colors contained in |
207 | | a given ColorStop vector against a given Color using the |
208 | | given intensity values. |
209 | | The intensity values fStartIntensity, fEndIntensity are |
210 | | in the range of [0.0 .. 1.0] and describe how much the |
211 | | blend is supposed to be done at the start color position |
212 | | and the end color position respectively, where 0.0 means |
213 | | to fully use the given BlendColor, 1.0 means to not change |
214 | | the existing color in the ColorStop. |
215 | | Every color entry in the given ColorStop is blended |
216 | | relative to it's StopPosition, interpolating the |
217 | | given intensities with the range [0.0 .. 1.0] to do so. |
218 | | */ |
219 | | void blendToIntensity(double fStartIntensity, double fEndIntensity, const BColor& rBlendColor); |
220 | | |
221 | | /* Tooling method to guarantee sort and correctness for |
222 | | the given ColorStops vector. |
223 | | A vector fulfilling these conditions is called to be |
224 | | in 'ordered state'. |
225 | | |
226 | | At return, the following conditions are guaranteed: |
227 | | - contains no ColorStops with offset < 0.0 (will |
228 | | be removed) |
229 | | - contains no ColorStops with offset > 1.0 (will |
230 | | be removed) |
231 | | - ColorStops with identical offsets are now allowed |
232 | | - will be sorted from lowest offset to highest |
233 | | |
234 | | Some more notes: |
235 | | - It can happen that the result is empty |
236 | | - It is allowed to have consecutive entries with |
237 | | the same color, this represents single-color |
238 | | regions inside the gradient |
239 | | - A entry with 0.0 is not required or forced, so |
240 | | no 'StartColor' is technically required |
241 | | - A entry with 1.0 is not required or forced, so |
242 | | no 'EndColor' is technically required |
243 | | |
244 | | All this is done in one run (sort + O(N)) without |
245 | | creating a copy of the data in any form |
246 | | */ |
247 | | void sortAndCorrect(); |
248 | | |
249 | | // check if we need last-ColorStop-correction. This returns true if the last |
250 | | // two ColorStops have the same offset but different Colors. In that case the |
251 | | // tessellation for gradients does have to create an extra ending/closing entry |
252 | | bool checkPenultimate() const; |
253 | | |
254 | | /* Tooling method to check if a ColorStop vector is defined |
255 | | by a single color. It returns true if this is the case. |
256 | | If true is returned, rSingleColor contains that single |
257 | | color for convenience. |
258 | | NOTE: If no ColorStop is defined, a fallback to BColor-default |
259 | | (which is black) and true will be returned |
260 | | */ |
261 | | bool isSingleColor(BColor& rSingleColor) const; |
262 | | |
263 | | /* Tooling method to reverse ColorStops, including offsets. |
264 | | When also mirroring offsets a valid sort keeps valid. |
265 | | */ |
266 | | void reverseColorStops(); |
267 | | |
268 | | // createSpaceAtStart creates fOffset space at start by |
269 | | // translating/scaling all entries to the right |
270 | | void createSpaceAtStart(double fOffset); |
271 | | |
272 | | // removeSpaceAtStart removes fOffset space from start by |
273 | | // translating/scaling entries more or equal to fOffset |
274 | | // to the left. Entries less than fOffset will be removed |
275 | | void removeSpaceAtStart(double fOffset); |
276 | | |
277 | | // try to detect if an empty/no-color-change area exists |
278 | | // at the start and return offset to it. Returns 0.0 if not. |
279 | | double detectPossibleOffsetAtStart() const; |
280 | | |
281 | | // returns true if the color stops are symmetrical in color and offset, otherwise false. |
282 | | bool isSymmetrical() const; |
283 | | // assume that the color stops represent an Axial gradient |
284 | | // and replace with gradient stops to represent the same |
285 | | // gradient as linear gradient |
286 | | void doApplyAxial(); |
287 | | |
288 | | // apply Steps as 'hard' color stops |
289 | | void doApplySteps(sal_uInt16 nStepCount); |
290 | | |
291 | | // Apply BColorModifierStack changes |
292 | | void tryToApplyBColorModifierStack(const BColorModifierStack& rBColorModifierStack); |
293 | | |
294 | | // check if local and given BColorStops have same count and distances, |
295 | | // ignore colors |
296 | | bool sameSizeAndDistances(const BColorStops& rComp) const; |
297 | | }; |
298 | | |
299 | | class BASEGFX_DLLPUBLIC BGradient final |
300 | | { |
301 | | private: |
302 | | css::awt::GradientStyle eStyle; |
303 | | |
304 | | // MCGS: ColorStops in the range [0.0 .. 1.0], including StartColor/EndColor |
305 | | basegfx::BColorStops aColorStops; |
306 | | |
307 | | Degree10 nAngle; |
308 | | sal_uInt16 nBorder; |
309 | | sal_uInt16 nOfsX; |
310 | | sal_uInt16 nOfsY; |
311 | | sal_uInt16 nIntensStart; |
312 | | sal_uInt16 nIntensEnd; |
313 | | sal_uInt16 nStepCount; |
314 | | |
315 | | static std::string GradientStyleToString(css::awt::GradientStyle eStyle); |
316 | | |
317 | | public: |
318 | | BGradient(); |
319 | | BGradient(const basegfx::BColorStops& rColorStops, |
320 | | css::awt::GradientStyle eStyle = css::awt::GradientStyle_LINEAR, |
321 | | Degree10 nAngle = 0_deg10, sal_uInt16 nXOfs = 50, sal_uInt16 nYOfs = 50, |
322 | | sal_uInt16 nBorder = 0, sal_uInt16 nStartIntens = 100, sal_uInt16 nEndIntens = 100, |
323 | | sal_uInt16 nSteps = 0); |
324 | | |
325 | | bool operator==(const BGradient& rGradient) const; |
326 | | |
327 | 61.1k | void SetGradientStyle(css::awt::GradientStyle eNewStyle) { eStyle = eNewStyle; } |
328 | | void SetColorStops(const basegfx::BColorStops& rSteps); |
329 | 63.2k | void SetAngle(Degree10 nNewAngle) { nAngle = nNewAngle; } |
330 | 4.36k | void SetBorder(sal_uInt16 nNewBorder) { nBorder = nNewBorder; } |
331 | 4.39k | void SetXOffset(sal_uInt16 nNewOffset) { nOfsX = nNewOffset; } |
332 | 4.39k | void SetYOffset(sal_uInt16 nNewOffset) { nOfsY = nNewOffset; } |
333 | 39.5k | void SetStartIntens(sal_uInt16 nNewIntens) { nIntensStart = nNewIntens; } |
334 | 39.5k | void SetEndIntens(sal_uInt16 nNewIntens) { nIntensEnd = nNewIntens; } |
335 | 4.36k | void SetSteps(sal_uInt16 nSteps) { nStepCount = nSteps; } |
336 | | |
337 | 8.53k | css::awt::GradientStyle GetGradientStyle() const { return eStyle; } |
338 | 4.56k | const basegfx::BColorStops& GetColorStops() const { return aColorStops; } |
339 | 4.05k | Degree10 GetAngle() const { return nAngle; } |
340 | 6.27k | sal_uInt16 GetBorder() const { return nBorder; } |
341 | 4.03k | sal_uInt16 GetXOffset() const { return nOfsX; } |
342 | 4.03k | sal_uInt16 GetYOffset() const { return nOfsY; } |
343 | 4.05k | sal_uInt16 GetStartIntens() const { return nIntensStart; } |
344 | 4.05k | sal_uInt16 GetEndIntens() const { return nIntensEnd; } |
345 | 4.03k | sal_uInt16 GetSteps() const { return nStepCount; } |
346 | | |
347 | | boost::property_tree::ptree dumpAsJSON() const; |
348 | | static BGradient fromJSON(std::u16string_view rJSON); |
349 | | |
350 | | // Tooling to handle |
351 | | // - border correction/integration |
352 | | // - apply StartStopIntensity to color stops |
353 | | // - convert type from 'axial' to linear |
354 | | // - apply Steps as 'hard' color stops |
355 | | void tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops = nullptr); |
356 | | void tryToApplyBorder(); |
357 | | void tryToApplyStartEndIntensity(); |
358 | | |
359 | | // If a linear gradient is symmetrical it is converted to an axial gradient. |
360 | | // Does nothing in other cases and for other gradient types. |
361 | | void tryToConvertToAxial(); |
362 | | void tryToApplyAxial(); |
363 | | void tryToApplySteps(); |
364 | | }; |
365 | | } |
366 | | |
367 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |