Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/primitive2d/fillgradientprimitive2d.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
21
#include <basegfx/polygon/b2dpolygon.hxx>
22
#include <basegfx/polygon/b2dpolygontools.hxx>
23
#include <texture/texture.hxx>
24
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
25
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
26
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
27
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
28
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
29
#include <utility>
30
31
32
using namespace com::sun::star;
33
34
35
namespace drawinglayer::primitive2d
36
{
37
        // Get the OuterColor. Take into account that for css::awt::GradientStyle_AXIAL
38
        // this is the last one due to inverted gradient usage (see constructor there)
39
        basegfx::BColor FillGradientPrimitive2D::getOuterColor() const
40
0
        {
41
0
            if (getFillGradient().getColorStops().empty())
42
0
                return basegfx::BColor();
43
44
0
            if (css::awt::GradientStyle_AXIAL == getFillGradient().getStyle())
45
0
                return getFillGradient().getColorStops().back().getStopColor();
46
47
0
            return getFillGradient().getColorStops().front().getStopColor();
48
0
        }
49
50
        // Get the needed UnitPolygon dependent on the GradientStyle
51
        basegfx::B2DPolygon FillGradientPrimitive2D::getUnitPolygon() const
52
0
        {
53
0
            if (css::awt::GradientStyle_RADIAL == getFillGradient().getStyle()
54
0
                || css::awt::GradientStyle_ELLIPTICAL == getFillGradient().getStyle())
55
0
            {
56
0
                return basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0);
57
0
            }
58
59
0
            return basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0));
60
0
        }
61
62
        void FillGradientPrimitive2D::generateMatricesAndColors(
63
            std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)> aCallback) const
64
0
        {
65
0
            switch(getFillGradient().getStyle())
66
0
            {
67
0
                default: // GradientStyle_MAKE_FIXED_SIZE
68
0
                case css::awt::GradientStyle_LINEAR:
69
0
                {
70
0
                    texture::GeoTexSvxGradientLinear aGradient(
71
0
                        getDefinitionRange(),
72
0
                        getOutputRange(),
73
0
                        getFillGradient().getSteps(),
74
0
                        getFillGradient().getColorStops(),
75
0
                        getFillGradient().getBorder(),
76
0
                        getFillGradient().getAngle());
77
0
                    aGradient.appendTransformationsAndColors(aCallback);
78
0
                    break;
79
0
                }
80
0
                case css::awt::GradientStyle_AXIAL:
81
0
                {
82
0
                    texture::GeoTexSvxGradientAxial aGradient(
83
0
                        getDefinitionRange(),
84
0
                        getOutputRange(),
85
0
                        getFillGradient().getSteps(),
86
0
                        getFillGradient().getColorStops(),
87
0
                        getFillGradient().getBorder(),
88
0
                        getFillGradient().getAngle());
89
0
                    aGradient.appendTransformationsAndColors(aCallback);
90
0
                    break;
91
0
                }
92
0
                case css::awt::GradientStyle_RADIAL:
93
0
                {
94
0
                    texture::GeoTexSvxGradientRadial aGradient(
95
0
                        getDefinitionRange(),
96
0
                        getFillGradient().getSteps(),
97
0
                        getFillGradient().getColorStops(),
98
0
                        getFillGradient().getBorder(),
99
0
                        getFillGradient().getOffsetX(),
100
0
                        getFillGradient().getOffsetY());
101
0
                    aGradient.appendTransformationsAndColors(aCallback);
102
0
                    break;
103
0
                }
104
0
                case css::awt::GradientStyle_ELLIPTICAL:
105
0
                {
106
0
                    texture::GeoTexSvxGradientElliptical aGradient(
107
0
                        getDefinitionRange(),
108
0
                        getFillGradient().getSteps(),
109
0
                        getFillGradient().getColorStops(),
110
0
                        getFillGradient().getBorder(),
111
0
                        getFillGradient().getOffsetX(),
112
0
                        getFillGradient().getOffsetY(),
113
0
                        getFillGradient().getAngle());
114
0
                    aGradient.appendTransformationsAndColors(aCallback);
115
0
                    break;
116
0
                }
117
0
                case css::awt::GradientStyle_SQUARE:
118
0
                {
119
0
                    texture::GeoTexSvxGradientSquare aGradient(
120
0
                        getDefinitionRange(),
121
0
                        getFillGradient().getSteps(),
122
0
                        getFillGradient().getColorStops(),
123
0
                        getFillGradient().getBorder(),
124
0
                        getFillGradient().getOffsetX(),
125
0
                        getFillGradient().getOffsetY(),
126
0
                        getFillGradient().getAngle());
127
0
                    aGradient.appendTransformationsAndColors(aCallback);
128
0
                    break;
129
0
                }
130
0
                case css::awt::GradientStyle_RECT:
131
0
                {
132
0
                    texture::GeoTexSvxGradientRect aGradient(
133
0
                        getDefinitionRange(),
134
0
                        getFillGradient().getSteps(),
135
0
                        getFillGradient().getColorStops(),
136
0
                        getFillGradient().getBorder(),
137
0
                        getFillGradient().getOffsetX(),
138
0
                        getFillGradient().getOffsetY(),
139
0
                        getFillGradient().getAngle());
140
0
                    aGradient.appendTransformationsAndColors(aCallback);
141
0
                    break;
142
0
                }
143
0
            }
144
0
        }
145
146
        Primitive2DReference FillGradientPrimitive2D::createFill(bool bOverlapping) const
147
0
        {
148
0
            Primitive2DContainer aContainer;
149
0
            if (bOverlapping)
150
0
            {
151
                // OverlappingFill: create solid fill with outmost color
152
0
                aContainer.push_back(
153
0
                    new PolyPolygonColorPrimitive2D(
154
0
                        basegfx::B2DPolyPolygon(
155
0
                            basegfx::utils::createPolygonFromRect(getOutputRange())),
156
0
                        getOuterColor()));
157
158
                // create solid fill steps by providing callback as lambda
159
0
                auto aCallback([&aContainer,this](
160
0
                    const basegfx::B2DHomMatrix& rMatrix,
161
0
                    const basegfx::BColor& rColor)
162
0
                {
163
                    // create part polygon
164
0
                    basegfx::B2DPolygon aNewPoly(getUnitPolygon());
165
0
                    aNewPoly.transform(rMatrix);
166
167
                    // create solid fill
168
0
                    aContainer.push_back(
169
0
                        new PolyPolygonColorPrimitive2D(
170
0
                            basegfx::B2DPolyPolygon(aNewPoly),
171
0
                            rColor));
172
0
                });
173
174
                // call value generator to trigger callbacks
175
0
                generateMatricesAndColors(aCallback);
176
0
            }
177
0
            else
178
0
            {
179
                // NonOverlappingFill
180
0
                if (getFillGradient().getColorStops().size() < 2)
181
0
                {
182
                    // not really a gradient, we need to create a start primitive
183
                    // entry using the single color and the covered area
184
0
                    const basegfx::B2DRange aOutmostRange(getOutputRange());
185
0
                    aContainer.push_back(
186
0
                        new PolyPolygonColorPrimitive2D(
187
0
                            basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aOutmostRange)),
188
0
                            getOuterColor()));
189
0
                }
190
0
                else
191
0
                {
192
                    // gradient with stops, prepare CombinedPolyPoly, use callback
193
0
                    basegfx::B2DPolyPolygon aCombinedPolyPoly;
194
0
                    basegfx::BColor aLastColor;
195
196
0
                    auto aCallback([&aContainer,&aCombinedPolyPoly,&aLastColor,this](
197
0
                        const basegfx::B2DHomMatrix& rMatrix,
198
0
                        const basegfx::BColor& rColor)
199
0
                    {
200
0
                        if (aContainer.empty())
201
0
                        {
202
                            // 1st callback, init CombinedPolyPoly & create 1st entry
203
0
                            basegfx::B2DRange aOutmostRange(getOutputRange());
204
205
                            // expand aOutmostRange with transformed first polygon
206
                            // to ensure confinement
207
0
                            basegfx::B2DPolygon aFirstPoly(getUnitPolygon());
208
0
                            aFirstPoly.transform(rMatrix);
209
0
                            aOutmostRange.expand(aFirstPoly.getB2DRange());
210
211
                            // build 1st combined polygon; outmost range 1st, then
212
                            // the shaped, transformed polygon
213
0
                            aCombinedPolyPoly.append(basegfx::utils::createPolygonFromRect(aOutmostRange));
214
0
                            aCombinedPolyPoly.append(aFirstPoly);
215
216
                            // create first primitive
217
0
                            aContainer.push_back(
218
0
                                new PolyPolygonColorPrimitive2D(
219
0
                                    aCombinedPolyPoly,
220
0
                                    getOuterColor()));
221
222
                            // save first polygon for re-use in next call, it's the second
223
                            // one, so remove 1st
224
0
                            aCombinedPolyPoly.remove(0);
225
226
                            // remember color for next primitive creation
227
0
                            aLastColor = rColor;
228
0
                        }
229
0
                        else
230
0
                        {
231
                            // regular n-th callback, create combined entry by re-using
232
                            // CombinedPolyPoly and aLastColor
233
0
                            basegfx::B2DPolygon aNextPoly(getUnitPolygon());
234
0
                            aNextPoly.transform(rMatrix);
235
0
                            aCombinedPolyPoly.append(aNextPoly);
236
237
                            // create primitive with correct color
238
0
                            aContainer.push_back(
239
0
                                new PolyPolygonColorPrimitive2D(
240
0
                                    aCombinedPolyPoly,
241
0
                                    aLastColor));
242
243
                            // prepare re-use of inner polygon, save color
244
0
                            aCombinedPolyPoly.remove(0);
245
0
                            aLastColor = rColor;
246
0
                        }
247
0
                    });
248
249
                    // call value generator to trigger callbacks
250
0
                    generateMatricesAndColors(aCallback);
251
252
                    // add last inner polygon with last color
253
0
                    aContainer.push_back(
254
0
                        new PolyPolygonColorPrimitive2D(
255
0
                            std::move(aCombinedPolyPoly),
256
0
                            aLastColor));
257
0
                }
258
0
            }
259
0
            return new GroupPrimitive2D(std::move(aContainer));
260
0
        }
261
262
        Primitive2DReference FillGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
263
0
        {
264
            // SDPR: support alpha directly now. If a primitive processor
265
            // cannot deal with it, use it's decomposition. For that purpose
266
            // this decomposition has two stages now: This 1st one will
267
            // (if needed) separate content and alpha into a TransparencePrimitive2D
268
            // and (if needed) embed that to a UnifiedTransparencePrimitive2D,
269
            // so all processors can work as before
270
0
            if (hasAlphaGradient() || hasTransparency())
271
0
            {
272
0
                Primitive2DReference aRetval(
273
0
                    new FillGradientPrimitive2D(
274
0
                        getOutputRange(),
275
0
                        getDefinitionRange(),
276
0
                        getFillGradient()));
277
278
0
                if (hasAlphaGradient())
279
0
                {
280
0
                    Primitive2DContainer aAlpha{ new FillGradientPrimitive2D(
281
0
                        getOutputRange(),
282
0
                        getDefinitionRange(),
283
0
                        getAlphaGradient()) };
284
285
0
                    aRetval = new TransparencePrimitive2D(Primitive2DContainer{ aRetval }, std::move(aAlpha));
286
0
                }
287
288
0
                if (hasTransparency())
289
0
                {
290
0
                    aRetval = new UnifiedTransparencePrimitive2D(Primitive2DContainer{ aRetval }, getTransparency());
291
0
                }
292
293
0
                return aRetval;
294
0
            }
295
296
            // default creates overlapping fill which works with AntiAliasing and without.
297
            // The non-overlapping version does not create single filled polygons, but
298
            // PolyPolygons where each one describes a 'ring' for the gradient such
299
            // that the rings will not overlap. This is useful for the old XOR-paint
300
            // 'trick' of VCL which is recorded in Metafiles; so this version may be
301
            // used from the MetafilePrimitive2D in its decomposition.
302
0
            if(!getFillGradient().isDefault())
303
0
            {
304
0
                return createFill(/*bOverlapping*/true);
305
0
            }
306
307
0
            return nullptr;
308
0
        }
309
310
        FillGradientPrimitive2D::FillGradientPrimitive2D(
311
            const basegfx::B2DRange& rOutputRange,
312
            const attribute::FillGradientAttribute& rFillGradient)
313
0
        : maOutputRange(rOutputRange)
314
0
        , maDefinitionRange(rOutputRange)
315
0
        , maFillGradient(rFillGradient)
316
0
        , maAlphaGradient()
317
0
        , mfTransparency(0.0)
318
0
        {
319
0
        }
320
321
        FillGradientPrimitive2D::FillGradientPrimitive2D(
322
            const basegfx::B2DRange& rOutputRange,
323
            const basegfx::B2DRange& rDefinitionRange,
324
            const attribute::FillGradientAttribute& rFillGradient,
325
            const attribute::FillGradientAttribute* pAlphaGradient,
326
            double fTransparency)
327
0
        : maOutputRange(rOutputRange)
328
0
        , maDefinitionRange(rDefinitionRange)
329
0
        , maFillGradient(rFillGradient)
330
0
        , maAlphaGradient()
331
0
        , mfTransparency(fTransparency)
332
0
        {
333
            // copy alpha gradient if we got one
334
0
            if (nullptr != pAlphaGradient)
335
0
                maAlphaGradient = *pAlphaGradient;
336
0
        }
337
338
        bool FillGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
339
0
        {
340
0
            if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive))
341
0
                return false;
342
343
0
            const FillGradientPrimitive2D& rCompare(static_cast<const FillGradientPrimitive2D&>(rPrimitive));
344
345
0
            if (getOutputRange() != rCompare.getOutputRange())
346
0
                return false;
347
348
0
            if (getDefinitionRange() != rCompare.getDefinitionRange())
349
0
                return false;
350
351
0
            if (getFillGradient() != rCompare.getFillGradient())
352
0
                return false;
353
354
0
            if (maAlphaGradient != rCompare.maAlphaGradient)
355
0
                return false;
356
357
0
            if (!basegfx::fTools::equal(getTransparency(), rCompare.getTransparency()))
358
0
                return false;
359
360
0
            return true;
361
0
        }
362
363
        basegfx::B2DRange FillGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
364
0
        {
365
            // return the geometrically visible area
366
0
            return getOutputRange();
367
0
        }
368
369
        // provide unique ID
370
        sal_uInt32 FillGradientPrimitive2D::getPrimitive2DID() const
371
0
        {
372
0
            return PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D;
373
0
        }
374
375
} // end of namespace
376
377
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */