summaryrefslogtreecommitdiff
path: root/include/basegfx/utils/bgradient.hxx
blob: 1f42b23c63212f54177fffba40d27a9ce11696c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <config_options.h>
#include <basegfx/color/bcolor.hxx>
#include <basegfx/basegfxdllapi.h>
#include <vector>
#include <com/sun/star/awt/Gradient2.hpp>
#include <com/sun/star/awt/GradientStyle.hpp>
#include <tools/degree.hxx>
#include <boost/property_tree/ptree_fwd.hpp>

namespace com
{
namespace sun
{
namespace star
{
namespace uno
{
class Any;
}
}
}
}

namespace basegfx
{
/* MCGR: Provide ColorStop definition

        This is the needed combination of offset and color:

        Offset is defined as:
        - being in the range of [0.0 .. 1.0] (unit range)
          - offsets outside are an error
        - lowest/1st value equivalent to StartColor
        - highest/last value equivalent to EndColor
        - missing 0.0/1.0 entries are allowed
        - at least one value (usually 0.0, StartColor) is required
            - this allows to avoid massive testing in all places where
              this data has to be accessed

        Color is defined as:
        - RGB with unit values [0.0 .. 1.0]

        These definitions are packed in a std::vector<ColorStop> ColorStops,
        see typedef below.
    */
class BASEGFX_DLLPUBLIC BColorStop
{
private:
    // offset in the range of [0.0 .. 1.0]
    double mfStopOffset;

    // RGB color of ColorStop entry
    BColor maStopColor;

public:
    // constructor - defaults are needed to have a default constructor
    // e.g. for usage in std::vector::insert (even when only reducing)
    // ensure [0.0 .. 1.0] range for mfStopOffset
    BColorStop(double fStopOffset = 0.0, const BColor& rStopColor = BColor())
        : mfStopOffset(fStopOffset)
        , maStopColor(rStopColor)
    {
        // NOTE: I originally *corrected* mfStopOffset here by using
        //   mfStopOffset(std::max(0.0, std::min(fOffset, 1.0)))
        // While that is formally correct, it moves an invalid
        // entry to 0.0 or 1.0, thus creating additional wrong
        // Start/EndColor entries. That may then 'overlay' the
        // correct entry when corrections are applied to the
        // vector of entries (see sortAndCorrectColorStops)
        // which leads to getting the wanted Start/EndColor
        // to be factically deleted, what is an error.
    }

    double getStopOffset() const { return mfStopOffset; }
    const BColor& getStopColor() const { return maStopColor; }

    // needed for std::sort
    bool operator<(const BColorStop& rCandidate) const
    {
        return getStopOffset() < rCandidate.getStopOffset();
    }

    bool operator==(const BColorStop& rCandidate) const
    {
        return getStopOffset() == rCandidate.getStopOffset()
               && getStopColor() == rCandidate.getStopColor();
    }
};

/* MCGR: Provide ColorStops definition to the FillGradientAttribute

        This array should be sorted ascending by offsets, from lowest to
        highest. Since all the primitive data definition where it is used
        is read-only, this can/will be guaranteed by forcing/checking this
        in the constructor, see ::FillGradientAttribute
    */
class BASEGFX_DLLPUBLIC BColorStops final : public std::vector<BColorStop>
{
private:
    void setColorStopSequence(const css::awt::ColorStopSequence& rColorStops);

public:
    explicit BColorStops()
        : vector()
    {
    }
    BColorStops(const BColorStops& other)
        : vector(other)
    {
    }
    BColorStops(BColorStops&& other) noexcept
        : vector(std::move(other))
    {
    }
    BColorStops(std::initializer_list<BColorStop> init)
        : vector(init)
    {
    }
    BColorStops(const_iterator first, const_iterator last)
        : vector(first, last)
    {
    }
    BColorStops(const css::awt::ColorStopSequence& rColorStops);

    // needs true == rVal.has<css::awt::ColorStopSequence>()
    BColorStops(const css::uno::Any& rVal);

    // constructor with two colors to explicitly create a
    // BColorStops for StartColor @0.0 & EndColor @1.0
    BColorStops(const BColor& rStart, const BColor& rEnd);

    BColorStops& operator=(const BColorStops& r)
    {
        vector::operator=(r);
        return *this;
    }
    BColorStops& operator=(BColorStops&& r) noexcept
    {
        vector::operator=(std::move(r));
        return *this;
    }

    // helper data struct to support buffering entries in
    // gradient texture mapping, see usages for more info
    struct BColorStopRange
    {
        basegfx::BColor maColorStart;
        basegfx::BColor maColorEnd;
        double mfOffsetStart;
        double mfOffsetEnd;

        BColorStopRange()
            : maColorStart()
            , maColorEnd()
            , mfOffsetStart(0.0)
            , mfOffsetEnd(0.0)
        {
        }
    };

    /* Helper to grep the correct ColorStop out of
           ColorStops and interpolate as needed for given
           relative value in fPosition in the range of [0.0 .. 1.0].
           It also takes care of evtl. given RequestedSteps.
        */
    BColor getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps,
                                 BColorStopRange& rLastColorStopRange) const;

    /* Tooling method that allows to replace the StartColor in a
           vector of ColorStops. A vector in 'ordered state' is expected,
           so you may use/have used sortAndCorrect.
           This method is for convenience & backwards compatibility, please
           think about handling multi-colored gradients directly.
        */
    void replaceStartColor(const BColor& rStart);

    /* Tooling method that allows to replace the EndColor in a
           vector of ColorStops. A vector in 'ordered state' is expected,
           so you may use/have used sortAndCorrect.
           This method is for convenience & backwards compatibility, please
           think about handling multi-colored gradients directly.
        */
    void replaceEndColor(const BColor& rEnd);

    /* Tooling method to linearly blend the Colors contained in
           a given ColorStop vector against a given Color using the
           given intensity values.
           The intensity values fStartIntensity, fEndIntensity are
           in the range of [0.0 .. 1.0] and describe how much the
           blend is supposed to be done at the start color position
           and the end color position respectively, where 0.0 means
           to fully use the given BlendColor, 1.0 means to not change
           the existing color in the ColorStop.
           Every color entry in the given ColorStop is blended
           relative to it's StopPosition, interpolating the
           given intensities with the range [0.0 .. 1.0] to do so.
        */
    void blendToIntensity(double fStartIntensity, double fEndIntensity, const BColor& rBlendColor);

    /* Tooling method to guarantee sort and correctness for
           the given ColorStops vector.
           A vector fulfilling these conditions is called to be
           in 'ordered state'.

           At return, the following conditions are guaranteed:
           - contains no ColorStops with offset < 0.0 (will
             be removed)
           - contains no ColorStops with offset > 1.0 (will
             be removed)
           - ColorStops with identical offsets are now allowed
           - will be sorted from lowest offset to highest

           Some more notes:
           - It can happen that the result is empty
           - It is allowed to have consecutive entries with
             the same color, this represents single-color
             regions inside the gradient
           - A entry with 0.0 is not required or forced, so
             no 'StartColor' is technically required
           - A entry with 1.0 is not required or forced, so
             no 'EndColor' is technically required

           All this is done in one run (sort + O(N)) without
           creating a copy of the data in any form
        */
    void sortAndCorrect();

    // check if we need last-ColorStop-correction. This returns true if the last
    // two ColorStops have the same offset but different Colors. In that case the
    // tessellation for gradients does have to create an extra ending/closing entry
    bool checkPenultimate() const;

    /* Tooling method to fill a awt::ColorStopSequence with
           the data from the given ColorStops. This is used in
           UNO API implementations.
        */
    css::awt::ColorStopSequence getAsColorStopSequence() const;

    /* Tooling method to check if a ColorStop vector is defined
            by a single color. It returns true if this is the case.
            If true is returned, rSingleColor contains that single
            color for convenience.
            NOTE: If no ColorStop is defined, a fallback to BColor-default
                    (which is black) and true will be returned
        */
    bool isSingleColor(BColor& rSingleColor) const;

    /* Tooling method to reverse ColorStops, including offsets.
           When also mirroring offsets a valid sort keeps valid.
        */
    void reverseColorStops();

    // createSpaceAtStart creates fOffset space at start by
    // translating/scaling all entries to the right
    void createSpaceAtStart(double fOffset);

    // removeSpaceAtStart removes fOffset space from start by
    // translating/scaling entries more or equal to fOffset
    // to the left. Entries less than fOffset will be removed
    void removeSpaceAtStart(double fOffset);

    // try to detect if an empty/no-color-change area exists
    // at the start and return offset to it. Returns 0.0 if not.
    double detectPossibleOffsetAtStart() const;

    // returns true if the color stops are symmetrical in color and offset, otherwise false.
    bool isSymmetrical() const;
};

class BASEGFX_DLLPUBLIC BGradient final
{
private:
    css::awt::GradientStyle eStyle;

    // MCGS: ColorStops in the range [0.0 .. 1.0], including StartColor/EndColor
    basegfx::BColorStops aColorStops;

    Degree10 nAngle;
    sal_uInt16 nBorder;
    sal_uInt16 nOfsX;
    sal_uInt16 nOfsY;
    sal_uInt16 nIntensStart;
    sal_uInt16 nIntensEnd;
    sal_uInt16 nStepCount;

    static std::string GradientStyleToString(css::awt::GradientStyle eStyle);
    void setGradient2(const css::awt::Gradient2& rGradient2);

public:
    BGradient();
    BGradient(const basegfx::BColorStops& rColorStops,
              css::awt::GradientStyle eStyle = css::awt::GradientStyle_LINEAR,
              Degree10 nAngle = 0_deg10, sal_uInt16 nXOfs = 50, sal_uInt16 nYOfs = 50,
              sal_uInt16 nBorder = 0, sal_uInt16 nStartIntens = 100, sal_uInt16 nEndIntens = 100,
              sal_uInt16 nSteps = 0);
    BGradient(const css::awt::Gradient2& rGradient2);

    // needs true == (rVal.has<css::awt::Gradient>() || rVal.has<css::awt::Gradient2>())
    BGradient(const css::uno::Any& rVal);

    bool operator==(const BGradient& rGradient) const;

    void SetGradientStyle(css::awt::GradientStyle eNewStyle) { eStyle = eNewStyle; }
    void SetColorStops(const basegfx::BColorStops& rSteps);
    void SetAngle(Degree10 nNewAngle) { nAngle = nNewAngle; }
    void SetBorder(sal_uInt16 nNewBorder) { nBorder = nNewBorder; }
    void SetXOffset(sal_uInt16 nNewOffset) { nOfsX = nNewOffset; }
    void SetYOffset(sal_uInt16 nNewOffset) { nOfsY = nNewOffset; }
    void SetStartIntens(sal_uInt16 nNewIntens) { nIntensStart = nNewIntens; }
    void SetEndIntens(sal_uInt16 nNewIntens) { nIntensEnd = nNewIntens; }
    void SetSteps(sal_uInt16 nSteps) { nStepCount = nSteps; }

    css::awt::GradientStyle GetGradientStyle() const { return eStyle; }
    const basegfx::BColorStops& GetColorStops() const { return aColorStops; }
    Degree10 GetAngle() const { return nAngle; }
    sal_uInt16 GetBorder() const { return nBorder; }
    sal_uInt16 GetXOffset() const { return nOfsX; }
    sal_uInt16 GetYOffset() const { return nOfsY; }
    sal_uInt16 GetStartIntens() const { return nIntensStart; }
    sal_uInt16 GetEndIntens() const { return nIntensEnd; }
    sal_uInt16 GetSteps() const { return nStepCount; }

    boost::property_tree::ptree dumpAsJSON() const;
    static BGradient fromJSON(std::u16string_view rJSON);

    /// Tooling method to fill awt::Gradient2 from data contained in the given basegfx::BGradient
    css::awt::Gradient2 getAsGradient2() const;

    /// Tooling to handle border correction/integration and StartStopIntensity
    void tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops = nullptr);
    void tryToApplyBorder();
    void tryToApplyStartEndIntensity();

    // If a linear gradient is symmetrical it is converted to an axial gradient.
    // Does nothing in other cases and for other gradient types.
    void tryToConvertToAxial();
};
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */