Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */