Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/texture/texture.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 <sal/config.h>
21
22
#include <algorithm>
23
24
#include <texture/texture.hxx>
25
#include <basegfx/numeric/ftools.hxx>
26
#include <basegfx/utils/gradienttools.hxx>
27
#include <basegfx/matrix/b2dhommatrixtools.hxx>
28
29
#include <comphelper/random.hxx>
30
31
namespace drawinglayer::texture
32
{
33
        namespace
34
        {
35
            double getRandomColorRange()
36
0
            {
37
0
                return comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX));
38
0
            }
39
        }
40
41
        GeoTexSvx::GeoTexSvx()
42
0
        {
43
0
        }
44
45
        GeoTexSvx::~GeoTexSvx()
46
0
        {
47
0
        }
48
49
        bool GeoTexSvx::operator==(const GeoTexSvx& /*rGeoTexSvx*/) const
50
0
        {
51
            // default implementation says yes (no data -> no difference)
52
0
            return true;
53
0
        }
54
55
        void GeoTexSvx::modifyBColor(const basegfx::B2DPoint& /*rUV*/, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
56
0
        {
57
            // base implementation creates random color (for testing only, may also be pure virtual)
58
0
            rBColor.setRed(getRandomColorRange());
59
0
            rBColor.setGreen(getRandomColorRange());
60
0
            rBColor.setBlue(getRandomColorRange());
61
0
        }
62
63
        void GeoTexSvx::modifyOpacity(const basegfx::B2DPoint& rUV, double& rfOpacity) const
64
0
        {
65
            // base implementation uses inverse of luminance of solved color (for testing only, may also be pure virtual)
66
0
            basegfx::BColor aBaseColor;
67
0
            modifyBColor(rUV, aBaseColor, rfOpacity);
68
0
            rfOpacity = 1.0 - aBaseColor.luminance();
69
0
        }
70
71
72
        GeoTexSvxGradient::GeoTexSvxGradient(
73
            const basegfx::B2DRange& rDefinitionRange,
74
            sal_uInt32 nRequestedSteps,
75
            const basegfx::BColorStops& rColorStops,
76
            double fBorder)
77
0
        : maDefinitionRange(rDefinitionRange)
78
0
        , mnRequestedSteps(nRequestedSteps)
79
0
        , mnColorStops(rColorStops)
80
0
        , mfBorder(fBorder)
81
0
        , maLastColorStopRange()
82
0
        {
83
0
        }
84
85
        GeoTexSvxGradient::~GeoTexSvxGradient()
86
0
        {
87
0
        }
88
89
        bool GeoTexSvxGradient::operator==(const GeoTexSvx& rGeoTexSvx) const
90
0
        {
91
0
            const GeoTexSvxGradient* pCompare = dynamic_cast< const GeoTexSvxGradient* >(&rGeoTexSvx);
92
93
0
            return (pCompare
94
0
                && maGradientInfo == pCompare->maGradientInfo
95
0
                && maDefinitionRange == pCompare->maDefinitionRange
96
0
                && mnRequestedSteps == pCompare->mnRequestedSteps
97
0
                && mnColorStops == pCompare->mnColorStops
98
0
                && mfBorder == pCompare->mfBorder);
99
0
        }
100
101
        GeoTexSvxGradientLinear::GeoTexSvxGradientLinear(
102
            const basegfx::B2DRange& rDefinitionRange,
103
            const basegfx::B2DRange& rOutputRange,
104
            sal_uInt32 nRequestedSteps,
105
            const basegfx::BColorStops& rColorStops,
106
            double fBorder,
107
            double fAngle)
108
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
109
0
        , mfUnitMinX(0.0)
110
0
        , mfUnitWidth(1.0)
111
0
        , mfUnitMaxY(1.0)
112
0
        {
113
0
            maGradientInfo = basegfx::utils::createLinearODFGradientInfo(
114
0
                rDefinitionRange,
115
0
                nRequestedSteps,
116
0
                fBorder,
117
0
                fAngle);
118
119
0
            if(rDefinitionRange != rOutputRange)
120
0
            {
121
0
                basegfx::B2DRange aInvOutputRange(rOutputRange);
122
123
0
                aInvOutputRange.transform(maGradientInfo.getBackTextureTransform());
124
0
                mfUnitMinX = aInvOutputRange.getMinX();
125
0
                mfUnitWidth = aInvOutputRange.getWidth();
126
0
                mfUnitMaxY = aInvOutputRange.getMaxY();
127
0
            }
128
0
        }
129
130
        GeoTexSvxGradientLinear::~GeoTexSvxGradientLinear()
131
        {
132
        }
133
134
        void GeoTexSvxGradientLinear::appendTransformationsAndColors(
135
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
136
0
        {
137
            // no color at all, done
138
0
            if (mnColorStops.empty())
139
0
                return;
140
141
            // only one color, done
142
0
            if (mnColorStops.size() < 2)
143
0
                return;
144
145
            // check if we need last-ColorStop-correction
146
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
147
148
0
            if (bPenultimateUsed)
149
0
            {
150
                // Here we need to temporarily add a ColorStop entry with the
151
                // same color as the last entry to correctly 'close' the
152
                // created gradient geometry.
153
                // The simplest way is to temporarily add an entry to the local
154
                // ColorStops for this at 1.0 (using same color)
155
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
156
0
            }
157
158
            // prepare unit range transform
159
0
            basegfx::B2DHomMatrix aPattern;
160
161
            // bring from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1]
162
0
            aPattern.scale(0.5, 0.5);
163
0
            aPattern.translate(0.5, 0.5);
164
165
            // scale and translate in X
166
0
            aPattern.scale(mfUnitWidth, 1.0);
167
0
            aPattern.translate(mfUnitMinX, 0.0);
168
169
            // outer loop over ColorStops, each is from cs_l to cs_r
170
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
171
0
            {
172
                // get offsets
173
0
                const double fOffsetStart(cs_l->getStopOffset());
174
0
                const double fOffsetEnd(cs_r->getStopOffset());
175
176
                // same offset, empty BColorStopRange, continue with next step
177
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
178
0
                    continue;
179
180
                // get colors & calculate steps
181
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
182
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
183
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
184
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
185
186
                // calculate StripeWidth
187
                // nSteps is >= 1, see getRequestedSteps, so no check needed here
188
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
189
190
                // for the 1st color range we do not need to create the 1st step
191
                // since it will be equal to StartColor and thus OuterColor, so
192
                // will be painted by the 1st, always-created background polygon
193
                // colored using OuterColor.
194
                // We *need* to create this though for all 'inner' color ranges
195
                // to get a correct start
196
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
197
198
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
199
0
                {
200
                    // calculate pos in Y
201
0
                    const double fPos(fOffsetStart + (fStripeWidth * innerLoop));
202
203
                    // scale and translate in Y. For GradientLinear we always have
204
                    // the full height
205
0
                    double fHeight(1.0 - fPos);
206
207
0
                    if (mfUnitMaxY > 1.0)
208
0
                    {
209
                        // extend when difference between definition and OutputRange exists
210
0
                        fHeight += mfUnitMaxY - 1.0;
211
0
                    }
212
213
0
                    basegfx::B2DHomMatrix aNew(aPattern);
214
0
                    aNew.scale(1.0, fHeight);
215
0
                    aNew.translate(0.0, fPos);
216
217
                    // set and add at target
218
0
                    rCallback(
219
0
                        maGradientInfo.getTextureTransform() * aNew,
220
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
221
0
                }
222
0
            }
223
224
0
            if (bPenultimateUsed)
225
0
            {
226
                // correct temporary change
227
0
                mnColorStops.pop_back();
228
0
            }
229
0
        }
230
231
        void GeoTexSvxGradientLinear::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
232
0
        {
233
            // no color at all, done
234
0
            if (mnColorStops.empty())
235
0
                return;
236
237
            // just single color, done
238
0
            if (mnColorStops.size() < 2)
239
0
            {
240
0
                rBColor = mnColorStops.front().getStopColor();
241
0
                return;
242
0
            }
243
244
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
245
0
            const double fScaler(basegfx::utils::getLinearGradientAlpha(rUV, maGradientInfo));
246
0
            rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange);
247
0
        }
248
249
        GeoTexSvxGradientAxial::GeoTexSvxGradientAxial(
250
            const basegfx::B2DRange& rDefinitionRange,
251
            const basegfx::B2DRange& rOutputRange,
252
            sal_uInt32 nRequestedSteps,
253
            const basegfx::BColorStops& rColorStops,
254
            double fBorder,
255
            double fAngle)
256
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
257
0
        , mfUnitMinX(0.0)
258
0
        , mfUnitWidth(1.0)
259
0
        {
260
            // ARGH! GradientAxial uses the ColorStops in reverse order compared
261
            // with the other gradients. Either stay 'thinking reverse' for the
262
            // rest of time or adapt it here and go in same order as the other five,
263
            // so unifications/tooling will be possible
264
0
            mnColorStops.reverseColorStops();
265
266
0
            maGradientInfo = basegfx::utils::createAxialODFGradientInfo(
267
0
                rDefinitionRange,
268
0
                nRequestedSteps,
269
0
                fBorder,
270
0
                fAngle);
271
272
0
            if(rDefinitionRange != rOutputRange)
273
0
            {
274
0
                basegfx::B2DRange aInvOutputRange(rOutputRange);
275
276
0
                aInvOutputRange.transform(maGradientInfo.getBackTextureTransform());
277
0
                mfUnitMinX = aInvOutputRange.getMinX();
278
0
                mfUnitWidth = aInvOutputRange.getWidth();
279
0
            }
280
0
        }
281
282
        GeoTexSvxGradientAxial::~GeoTexSvxGradientAxial()
283
        {
284
        }
285
286
        void GeoTexSvxGradientAxial::appendTransformationsAndColors(
287
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
288
0
        {
289
            // no color at all, done
290
0
            if (mnColorStops.empty())
291
0
                return;
292
293
            // only one color, done
294
0
            if (mnColorStops.size() < 2)
295
0
                return;
296
297
            // check if we need last-ColorStop-correction
298
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
299
300
0
            if (bPenultimateUsed)
301
0
            {
302
                // temporarily add a ColorStop entry
303
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
304
0
            }
305
306
            // prepare unit range transform
307
0
            basegfx::B2DHomMatrix aPattern;
308
309
            // bring in X from unit circle [-1, -1, 1, 1] to unit range [0, 0, 1, 1]
310
0
            aPattern.scale(0.5, 1.0);
311
0
            aPattern.translate(0.5, 0.0);
312
313
            // scale/translate in X
314
0
            aPattern.scale(mfUnitWidth, 1.0);
315
0
            aPattern.translate(mfUnitMinX, 0.0);
316
317
            // outer loop over ColorStops, each is from cs_l to cs_r
318
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
319
0
            {
320
                // get offsets
321
0
                const double fOffsetStart(cs_l->getStopOffset());
322
0
                const double fOffsetEnd(cs_r->getStopOffset());
323
324
                // same offset, empty BColorStopRange, continue with next step
325
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
326
0
                    continue;
327
328
                // get colors & calculate steps
329
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
330
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
331
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
332
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
333
334
                // calculate StripeWidth
335
                // nSteps is >= 1, see getRequestedSteps, so no check needed here
336
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
337
338
                // for the 1st color range we do not need to create the 1st step, see above
339
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
340
341
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
342
0
                {
343
                    // calculate pos in Y
344
0
                    const double fPos(fOffsetStart + (fStripeWidth * innerLoop));
345
346
                    // already centered in Y on X-Axis, just scale in Y
347
0
                    basegfx::B2DHomMatrix aNew(aPattern);
348
0
                    aNew.scale(1.0, 1.0 - fPos);
349
350
                    // set and add at target
351
0
                    rCallback(
352
0
                        maGradientInfo.getTextureTransform() * aNew,
353
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
354
0
                }
355
0
            }
356
357
0
            if (bPenultimateUsed)
358
0
            {
359
                // correct temporary change
360
0
                mnColorStops.pop_back();
361
0
            }
362
0
        }
363
364
        void GeoTexSvxGradientAxial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
365
0
        {
366
            // no color at all, done
367
0
            if (mnColorStops.empty())
368
0
                return;
369
370
            // just single color, done
371
0
            if (mnColorStops.size() < 2)
372
0
            {
373
                // we use the reverse ColorSteps here, so use front value
374
0
                rBColor = mnColorStops.front().getStopColor();
375
0
                return;
376
0
            }
377
378
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
379
0
            const double fScaler(basegfx::utils::getAxialGradientAlpha(rUV, maGradientInfo));
380
381
                // we use the reverse ColorSteps here, so mirror scaler value
382
0
            rBColor = mnColorStops.getInterpolatedBColor(1.0 - fScaler, mnRequestedSteps, maLastColorStopRange);
383
0
        }
384
385
386
        GeoTexSvxGradientRadial::GeoTexSvxGradientRadial(
387
            const basegfx::B2DRange& rDefinitionRange,
388
            sal_uInt32 nRequestedSteps,
389
            const basegfx::BColorStops& rColorStops,
390
            double fBorder,
391
            double fOffsetX,
392
            double fOffsetY)
393
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
394
0
        {
395
0
            maGradientInfo = basegfx::utils::createRadialODFGradientInfo(
396
0
                rDefinitionRange,
397
0
                basegfx::B2DVector(fOffsetX,fOffsetY),
398
0
                nRequestedSteps,
399
0
                fBorder);
400
0
        }
401
402
        GeoTexSvxGradientRadial::~GeoTexSvxGradientRadial()
403
        {
404
        }
405
406
        void GeoTexSvxGradientRadial::appendTransformationsAndColors(
407
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
408
0
        {
409
            // no color at all, done
410
0
            if (mnColorStops.empty())
411
0
                return;
412
413
            // only one color, done
414
0
            if (mnColorStops.size() < 2)
415
0
                return;
416
417
            // check if we need last-ColorStop-correction
418
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
419
420
0
            if (bPenultimateUsed)
421
0
            {
422
                // temporarily add a ColorStop entry
423
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
424
0
            }
425
426
            // outer loop over ColorStops, each is from cs_l to cs_r
427
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
428
0
            {
429
                // get offsets
430
0
                const double fOffsetStart(cs_l->getStopOffset());
431
0
                const double fOffsetEnd(cs_r->getStopOffset());
432
433
                // same offset, empty BColorStopRange, continue with next step
434
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
435
0
                    continue;
436
437
                // get colors & calculate steps
438
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
439
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
440
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
441
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
442
443
                // calculate StripeWidth
444
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
445
446
                // get correct start for inner loop (see above)
447
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
448
449
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
450
0
                {
451
                    // calculate size/radius
452
0
                    const double fSize(1.0 - (fOffsetStart + (fStripeWidth * innerLoop)));
453
454
                    // set and add at target
455
0
                    rCallback(
456
0
                        maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize),
457
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
458
0
                }
459
0
            }
460
461
0
            if (bPenultimateUsed)
462
0
            {
463
                // correct temporary change
464
0
                mnColorStops.pop_back();
465
0
            }
466
0
        }
467
468
        void GeoTexSvxGradientRadial::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
469
0
        {
470
            // no color at all, done
471
0
            if (mnColorStops.empty())
472
0
                return;
473
474
            // just single color, done
475
0
            if (mnColorStops.size() < 2)
476
0
            {
477
0
                rBColor = mnColorStops.front().getStopColor();
478
0
                return;
479
0
            }
480
481
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
482
0
            const double fScaler(basegfx::utils::getRadialGradientAlpha(rUV, maGradientInfo));
483
0
            rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange);
484
0
        }
485
486
487
        GeoTexSvxGradientElliptical::GeoTexSvxGradientElliptical(
488
            const basegfx::B2DRange& rDefinitionRange,
489
            sal_uInt32 nRequestedSteps,
490
            const basegfx::BColorStops& rColorStops,
491
            double fBorder,
492
            double fOffsetX,
493
            double fOffsetY,
494
            double fAngle)
495
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
496
0
        {
497
0
            maGradientInfo = basegfx::utils::createEllipticalODFGradientInfo(
498
0
                rDefinitionRange,
499
0
                basegfx::B2DVector(fOffsetX,fOffsetY),
500
0
                nRequestedSteps,
501
0
                fBorder,
502
0
                fAngle);
503
0
        }
504
505
        GeoTexSvxGradientElliptical::~GeoTexSvxGradientElliptical()
506
        {
507
        }
508
509
        void GeoTexSvxGradientElliptical::appendTransformationsAndColors(
510
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
511
0
        {
512
            // no color at all, done
513
0
            if (mnColorStops.empty())
514
0
                return;
515
516
            // only one color, done
517
0
            if (mnColorStops.size() < 2)
518
0
                return;
519
520
            // check if we need last-ColorStop-correction
521
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
522
523
0
            if (bPenultimateUsed)
524
0
            {
525
                // temporarily add a ColorStop entry
526
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
527
0
            }
528
529
            // prepare vars dependent on aspect ratio
530
0
            const double fAR(maGradientInfo.getAspectRatio());
531
0
            const bool bMTO(fAR > 1.0);
532
533
            // outer loop over ColorStops, each is from cs_l to cs_r
534
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
535
0
            {
536
                // get offsets
537
0
                const double fOffsetStart(cs_l->getStopOffset());
538
0
                const double fOffsetEnd(cs_r->getStopOffset());
539
540
                // same offset, empty BColorStopRange, continue with next step
541
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
542
0
                    continue;
543
544
                // get colors & calculate steps
545
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
546
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
547
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
548
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
549
550
                // calculate StripeWidth
551
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
552
553
                // get correct start for inner loop (see above)
554
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
555
556
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
557
0
                {
558
                    // calculate offset position for entry
559
0
                    const double fSize(fOffsetStart + (fStripeWidth * innerLoop));
560
561
                    // set and add at target
562
0
                    rCallback(
563
0
                        maGradientInfo.getTextureTransform()
564
0
                        * basegfx::utils::createScaleB2DHomMatrix(
565
0
                            1.0 - (bMTO ? fSize / fAR : fSize),
566
0
                            1.0 - (bMTO ? fSize : fSize * fAR)),
567
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
568
0
                }
569
0
            }
570
571
0
            if (bPenultimateUsed)
572
0
            {
573
                // correct temporary change
574
0
                mnColorStops.pop_back();
575
0
            }
576
0
        }
577
578
        void GeoTexSvxGradientElliptical::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
579
0
        {
580
            // no color at all, done
581
0
            if (mnColorStops.empty())
582
0
                return;
583
584
            // just single color, done
585
0
            if (mnColorStops.size() < 2)
586
0
            {
587
0
                rBColor = mnColorStops.front().getStopColor();
588
0
                return;
589
0
            }
590
591
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
592
0
            const double fScaler(basegfx::utils::getEllipticalGradientAlpha(rUV, maGradientInfo));
593
0
            rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange);
594
0
        }
595
596
597
        GeoTexSvxGradientSquare::GeoTexSvxGradientSquare(
598
            const basegfx::B2DRange& rDefinitionRange,
599
            sal_uInt32 nRequestedSteps,
600
            const basegfx::BColorStops& rColorStops,
601
            double fBorder,
602
            double fOffsetX,
603
            double fOffsetY,
604
            double fAngle)
605
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
606
0
        {
607
0
            maGradientInfo = basegfx::utils::createSquareODFGradientInfo(
608
0
                rDefinitionRange,
609
0
                basegfx::B2DVector(fOffsetX,fOffsetY),
610
0
                nRequestedSteps,
611
0
                fBorder,
612
0
                fAngle);
613
0
        }
614
615
        GeoTexSvxGradientSquare::~GeoTexSvxGradientSquare()
616
        {
617
        }
618
619
        void GeoTexSvxGradientSquare::appendTransformationsAndColors(
620
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
621
0
        {
622
            // no color at all, done
623
0
            if (mnColorStops.empty())
624
0
                return;
625
626
            // only one color, done
627
0
            if (mnColorStops.size() < 2)
628
0
                return;
629
630
            // check if we need last-ColorStop-correction
631
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
632
633
0
            if (bPenultimateUsed)
634
0
            {
635
                // temporarily add a ColorStop entry
636
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
637
0
            }
638
639
            // outer loop over ColorStops, each is from cs_l to cs_r
640
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
641
0
            {
642
                // get offsets
643
0
                const double fOffsetStart(cs_l->getStopOffset());
644
0
                const double fOffsetEnd(cs_r->getStopOffset());
645
646
                // same offset, empty BColorStopRange, continue with next step
647
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
648
0
                    continue;
649
650
                // get colors & calculate steps
651
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
652
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
653
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
654
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
655
656
                // calculate StripeWidth
657
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
658
659
                // get correct start for inner loop (see above)
660
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
661
662
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
663
0
                {
664
                    // calculate size/radius
665
0
                    const double fSize(1.0 - (fOffsetStart + (fStripeWidth * innerLoop)));
666
667
                    // set and add at target
668
0
                    rCallback(
669
0
                        maGradientInfo.getTextureTransform() * basegfx::utils::createScaleB2DHomMatrix(fSize, fSize),
670
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
671
0
                }
672
0
            }
673
674
0
            if (bPenultimateUsed)
675
0
            {
676
                // correct temporary change
677
0
                mnColorStops.pop_back();
678
0
            }
679
0
        }
680
681
        void GeoTexSvxGradientSquare::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
682
0
        {
683
            // no color at all, done
684
0
            if (mnColorStops.empty())
685
0
                return;
686
687
            // just single color, done
688
0
            if (mnColorStops.size() < 2)
689
0
            {
690
0
                rBColor = mnColorStops.front().getStopColor();
691
0
                return;
692
0
            }
693
694
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
695
0
            const double fScaler(basegfx::utils::getSquareGradientAlpha(rUV, maGradientInfo));
696
0
            rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange);
697
0
        }
698
699
700
        GeoTexSvxGradientRect::GeoTexSvxGradientRect(
701
            const basegfx::B2DRange& rDefinitionRange,
702
            sal_uInt32 nRequestedSteps,
703
            const basegfx::BColorStops& rColorStops,
704
            double fBorder,
705
            double fOffsetX,
706
            double fOffsetY,
707
            double fAngle)
708
0
        : GeoTexSvxGradient(rDefinitionRange, nRequestedSteps, rColorStops, fBorder)
709
0
        {
710
0
            maGradientInfo = basegfx::utils::createRectangularODFGradientInfo(
711
0
                rDefinitionRange,
712
0
                basegfx::B2DVector(fOffsetX,fOffsetY),
713
0
                nRequestedSteps,
714
0
                fBorder,
715
0
                fAngle);
716
0
        }
717
718
        GeoTexSvxGradientRect::~GeoTexSvxGradientRect()
719
        {
720
        }
721
722
        void GeoTexSvxGradientRect::appendTransformationsAndColors(
723
            const std::function<void(const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor)>& rCallback)
724
0
        {
725
            // no color at all, done
726
0
            if (mnColorStops.empty())
727
0
                return;
728
729
            // only one color, done
730
0
            if (mnColorStops.size() < 2)
731
0
                return;
732
733
            // check if we need last-ColorStop-correction
734
0
            const bool bPenultimateUsed(mnColorStops.checkPenultimate());
735
736
0
            if (bPenultimateUsed)
737
0
            {
738
                // temporarily add a ColorStop entry
739
0
                mnColorStops.emplace_back(1.0, mnColorStops.back().getStopColor());
740
0
            }
741
742
            // prepare vars dependent on aspect ratio
743
0
            const double fAR(maGradientInfo.getAspectRatio());
744
0
            const bool bMTO(fAR > 1.0);
745
746
            // outer loop over ColorStops, each is from cs_l to cs_r
747
0
            for (auto cs_l(mnColorStops.begin()), cs_r(cs_l + 1); cs_r != mnColorStops.end(); cs_l++, cs_r++)
748
0
            {
749
                // get offsets
750
0
                const double fOffsetStart(cs_l->getStopOffset());
751
0
                const double fOffsetEnd(cs_r->getStopOffset());
752
753
                // same offset, empty BColorStopRange, continue with next step
754
0
                if (basegfx::fTools::equal(fOffsetStart, fOffsetEnd))
755
0
                    continue;
756
757
                // get colors & calculate steps
758
0
                const basegfx::BColor aCStart(cs_l->getStopColor());
759
0
                const basegfx::BColor aCEnd(cs_r->getStopColor());
760
0
                const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
761
0
                    maGradientInfo.getRequestedSteps(), aCStart, aCEnd));
762
763
                // calculate StripeWidth
764
0
                const double fStripeWidth((fOffsetEnd - fOffsetStart) / nSteps);
765
766
                // get correct start for inner loop (see above)
767
0
                const sal_uInt32 nStartInnerLoop(cs_l == mnColorStops.begin() ? 1 : 0);
768
769
0
                for (sal_uInt32 innerLoop(nStartInnerLoop); innerLoop < nSteps; innerLoop++)
770
0
                {
771
                    // calculate offset position for entry
772
0
                    const double fSize(fOffsetStart + (fStripeWidth * innerLoop));
773
774
                    // set and add at target
775
0
                    rCallback(
776
0
                        maGradientInfo.getTextureTransform()
777
0
                        * basegfx::utils::createScaleB2DHomMatrix(
778
0
                            1.0 - (bMTO ? fSize / fAR : fSize),
779
0
                            1.0 - (bMTO ? fSize : fSize * fAR)),
780
0
                        1 == nSteps ? aCStart : basegfx::BColor(interpolate(aCStart, aCEnd, double(innerLoop) / double(nSteps - 1))));
781
0
                }
782
0
            }
783
784
0
            if (bPenultimateUsed)
785
0
            {
786
                // correct temporary change
787
0
                mnColorStops.pop_back();
788
0
            }
789
0
        }
790
791
        void GeoTexSvxGradientRect::modifyBColor(const basegfx::B2DPoint& rUV, basegfx::BColor& rBColor, double& /*rfOpacity*/) const
792
0
        {
793
            // no color at all, done
794
0
            if (mnColorStops.empty())
795
0
                return;
796
797
            // just single color, done
798
0
            if (mnColorStops.size() < 2)
799
0
            {
800
0
                rBColor = mnColorStops.front().getStopColor();
801
0
                return;
802
0
            }
803
804
            // texture-back-transform X/Y -> t [0.0..1.0] and determine color
805
0
            const double fScaler(basegfx::utils::getRectangularGradientAlpha(rUV, maGradientInfo));
806
0
            rBColor = mnColorStops.getInterpolatedBColor(fScaler, mnRequestedSteps, maLastColorStopRange);
807
0
        }
808
809
810
        GeoTexSvxHatch::GeoTexSvxHatch(
811
            const basegfx::B2DRange& rDefinitionRange,
812
            const basegfx::B2DRange& rOutputRange,
813
            double fDistance,
814
            double fAngle)
815
0
        :   maOutputRange(rOutputRange),
816
0
            mfDistance(0.1),
817
0
            mfAngle(fAngle),
818
0
            mnSteps(10),
819
0
            mbDefinitionRangeEqualsOutputRange(rDefinitionRange == rOutputRange)
820
0
        {
821
0
            double fTargetSizeX(rDefinitionRange.getWidth());
822
0
            double fTargetSizeY(rDefinitionRange.getHeight());
823
0
            double fTargetOffsetX(rDefinitionRange.getMinX());
824
0
            double fTargetOffsetY(rDefinitionRange.getMinY());
825
826
0
            fAngle = -fAngle;
827
828
            // add object expansion
829
0
            if(0.0 != fAngle)
830
0
            {
831
0
                const double fAbsCos(fabs(cos(fAngle)));
832
0
                const double fAbsSin(fabs(sin(fAngle)));
833
0
                const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
834
0
                const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
835
0
                fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
836
0
                fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
837
0
                fTargetSizeX = fNewX;
838
0
                fTargetSizeY = fNewY;
839
0
            }
840
841
            // add object scale before rotate
842
0
            maTextureTransform.scale(fTargetSizeX, fTargetSizeY);
843
844
            // add texture rotate after scale to keep perpendicular angles
845
0
            if(0.0 != fAngle)
846
0
            {
847
0
                basegfx::B2DPoint aCenter(0.5, 0.5);
848
0
                aCenter *= maTextureTransform;
849
850
0
                maTextureTransform = basegfx::utils::createRotateAroundPoint(aCenter, fAngle)
851
0
                    * maTextureTransform;
852
0
            }
853
854
            // add object translate
855
0
            maTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
856
857
            // prepare height for texture
858
0
            const double fSteps((0.0 != fDistance) ? fTargetSizeY / fDistance : 10.0);
859
0
            mnSteps = basegfx::fround(fSteps + 0.5);
860
0
            mfDistance = 1.0 / fSteps;
861
0
        }
862
863
        GeoTexSvxHatch::~GeoTexSvxHatch()
864
        {
865
        }
866
867
        bool GeoTexSvxHatch::operator==(const GeoTexSvx& rGeoTexSvx) const
868
0
        {
869
0
            const GeoTexSvxHatch* pCompare = dynamic_cast< const GeoTexSvxHatch* >(&rGeoTexSvx);
870
0
            return (pCompare
871
0
                && maOutputRange == pCompare->maOutputRange
872
0
                && maTextureTransform == pCompare->maTextureTransform
873
0
                && mfDistance == pCompare->mfDistance
874
0
                && mfAngle == pCompare->mfAngle
875
0
                && mnSteps == pCompare->mnSteps);
876
0
        }
877
878
        void GeoTexSvxHatch::appendTransformations(std::vector< basegfx::B2DHomMatrix >& rMatrices)
879
0
        {
880
0
            if(mbDefinitionRangeEqualsOutputRange)
881
0
            {
882
                // simple hatch where the definition area equals the output area
883
0
                for(sal_uInt32 a(1); a < mnSteps; a++)
884
0
                {
885
                    // create matrix
886
0
                    const double fOffset(mfDistance * static_cast<double>(a));
887
0
                    basegfx::B2DHomMatrix aNew;
888
0
                    aNew.set(1, 2, fOffset);
889
0
                    rMatrices.push_back(maTextureTransform * aNew);
890
0
                }
891
0
            }
892
0
            else
893
0
            {
894
                // output area is different from definition area, back-transform to get
895
                // the output area in unit coordinates and fill this with hatch lines
896
                // using the settings derived from the definition area
897
0
                basegfx::B2DRange aBackUnitRange(maOutputRange);
898
899
0
                aBackUnitRange.transform(getBackTextureTransform());
900
901
                // calculate vertical start value and a security maximum integer value to avoid death loops
902
0
                double fStart(basegfx::snapToNearestMultiple(aBackUnitRange.getMinY(), mfDistance));
903
0
                const sal_uInt32 nNeededIntegerSteps(basegfx::fround((aBackUnitRange.getHeight() / mfDistance) + 0.5));
904
0
                sal_uInt32 nMaxIntegerSteps(std::min(nNeededIntegerSteps, sal_uInt32(10000)));
905
906
0
                while(fStart < aBackUnitRange.getMaxY() && nMaxIntegerSteps)
907
0
                {
908
                    // create new transform for
909
0
                    basegfx::B2DHomMatrix aNew;
910
911
                    // adapt x scale and position
912
                    //aNew.scale(aBackUnitRange.getWidth(), 1.0);
913
                    //aNew.translate(aBackUnitRange.getMinX(), 0.0);
914
0
                    aNew.set(0, 0, aBackUnitRange.getWidth());
915
0
                    aNew.set(0, 2, aBackUnitRange.getMinX());
916
917
                    // adapt y position to current step
918
0
                    aNew.set(1, 2, fStart);
919
                    //aNew.translate(0.0, fStart);
920
921
                    // add new transformation
922
0
                    rMatrices.push_back(maTextureTransform * aNew);
923
924
                    // next step
925
0
                    fStart += mfDistance;
926
0
                    nMaxIntegerSteps--;
927
0
                }
928
0
            }
929
0
        }
930
931
        double GeoTexSvxHatch::getDistanceToHatch(const basegfx::B2DPoint& rUV) const
932
0
        {
933
            // the below is an inlined and optimised version of
934
            //     const basegfx::B2DPoint aCoor(getBackTextureTransform() * rUV);
935
            //     return fmod(aCoor.getY(), mfDistance);
936
937
0
            const basegfx::B2DHomMatrix& rMat = getBackTextureTransform();
938
0
            double fX = rUV.getX();
939
0
            double fY = rUV.getY();
940
941
0
            double fTempY(
942
0
                rMat.get(1, 0) * fX +
943
0
                rMat.get(1, 1) * fY +
944
0
                rMat.get(1, 2));
945
946
0
            return fmod(fTempY, mfDistance);
947
0
        }
948
949
        const basegfx::B2DHomMatrix& GeoTexSvxHatch::getBackTextureTransform() const
950
0
        {
951
0
            if(maBackTextureTransform.isIdentity())
952
0
            {
953
0
                const_cast< GeoTexSvxHatch* >(this)->maBackTextureTransform = maTextureTransform;
954
0
                const_cast< GeoTexSvxHatch* >(this)->maBackTextureTransform.invert();
955
0
            }
956
957
0
            return maBackTextureTransform;
958
0
        }
959
960
961
        GeoTexSvxTiled::GeoTexSvxTiled(
962
            const basegfx::B2DRange& rRange,
963
            double fOffsetX,
964
            double fOffsetY)
965
0
        :   maRange(rRange),
966
0
            mfOffsetX(std::clamp(fOffsetX, 0.0, 1.0)),
967
0
            mfOffsetY(std::clamp(fOffsetY, 0.0, 1.0))
968
0
        {
969
0
            if(!basegfx::fTools::equalZero(mfOffsetX))
970
0
            {
971
0
                mfOffsetY = 0.0;
972
0
            }
973
0
        }
974
975
        GeoTexSvxTiled::~GeoTexSvxTiled()
976
        {
977
        }
978
979
        bool GeoTexSvxTiled::operator==(const GeoTexSvx& rGeoTexSvx) const
980
0
        {
981
0
            const GeoTexSvxTiled* pCompare = dynamic_cast< const GeoTexSvxTiled* >(&rGeoTexSvx);
982
983
0
            return (pCompare
984
0
                && maRange == pCompare->maRange
985
0
                && mfOffsetX == pCompare->mfOffsetX
986
0
                && mfOffsetY == pCompare->mfOffsetY);
987
0
        }
988
989
        sal_uInt32 GeoTexSvxTiled::getNumberOfTiles() const
990
0
        {
991
0
            sal_Int32 nTiles = 0;
992
0
            iterateTiles([&](double, double) { ++nTiles; });
993
0
            return nTiles;
994
0
        }
995
996
        void GeoTexSvxTiled::appendTransformations(std::vector< basegfx::B2DHomMatrix >& rMatrices) const
997
0
        {
998
0
            const double fWidth(maRange.getWidth());
999
0
            const double fHeight(maRange.getHeight());
1000
0
            iterateTiles([&](double fPosX, double fPosY) {
1001
0
                rMatrices.push_back(basegfx::utils::createScaleTranslateB2DHomMatrix(
1002
0
                                        fWidth,
1003
0
                                        fHeight,
1004
0
                                        fPosX,
1005
0
                                        fPosY));
1006
0
                });
1007
0
        }
1008
1009
        void GeoTexSvxTiled::iterateTiles(std::function<void(double fPosX, double fPosY)> aFunc) const
1010
0
        {
1011
0
            const double fWidth(maRange.getWidth());
1012
1013
0
            if(basegfx::fTools::equalZero(fWidth))
1014
0
                return;
1015
1016
0
            const double fHeight(maRange.getHeight());
1017
1018
0
            if(basegfx::fTools::equalZero(fHeight))
1019
0
                return;
1020
1021
0
            double fStartX(maRange.getMinX());
1022
0
            double fStartY(maRange.getMinY());
1023
0
            sal_Int32 nPosX(0);
1024
0
            sal_Int32 nPosY(0);
1025
1026
0
            if(fStartX > 0.0)
1027
0
            {
1028
0
                const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartX / fWidth)) + 1);
1029
1030
0
                nPosX -= nDiff;
1031
0
                fStartX -= nDiff * fWidth;
1032
0
            }
1033
1034
0
            if((fStartX + fWidth) < 0.0)
1035
0
            {
1036
0
                const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartX / fWidth)));
1037
1038
0
                nPosX += nDiff;
1039
0
                fStartX += nDiff * fWidth;
1040
0
            }
1041
1042
0
            if(fStartY > 0.0)
1043
0
            {
1044
0
                const sal_Int32 nDiff(static_cast<sal_Int32>(floor(fStartY / fHeight)) + 1);
1045
1046
0
                nPosY -= nDiff;
1047
0
                fStartY -= nDiff * fHeight;
1048
0
            }
1049
1050
0
            if((fStartY + fHeight) < 0.0)
1051
0
            {
1052
0
                const sal_Int32 nDiff(static_cast<sal_Int32>(floor(-fStartY / fHeight)));
1053
1054
0
                nPosY += nDiff;
1055
0
                fStartY += nDiff * fHeight;
1056
0
            }
1057
1058
0
            if(!basegfx::fTools::equalZero(mfOffsetY))
1059
0
            {
1060
0
                for(double fPosX(fStartX); basegfx::fTools::less(fPosX, 1.0); fPosX += fWidth, nPosX++)
1061
0
                {
1062
0
                    for(double fPosY((nPosX % 2) ? fStartY - fHeight + (mfOffsetY * fHeight) : fStartY);
1063
0
                        basegfx::fTools::less(fPosY, 1.0); fPosY += fHeight)
1064
0
                        aFunc(fPosX, fPosY);
1065
0
                }
1066
0
            }
1067
0
            else
1068
0
            {
1069
0
                for(double fPosY(fStartY); basegfx::fTools::less(fPosY, 1.0); fPosY += fHeight, nPosY++)
1070
0
                {
1071
0
                    for(double fPosX((nPosY % 2) ? fStartX - fWidth + (mfOffsetX * fWidth) : fStartX);
1072
0
                        basegfx::fTools::less(fPosX, 1.0); fPosX += fWidth)
1073
0
                        aFunc(fPosX, fPosY);
1074
0
                }
1075
0
            }
1076
1077
0
        }
1078
1079
} // end of namespace
1080
1081
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */