Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/basegfx/source/tools/gradienttools.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 <basegfx/utils/gradienttools.hxx>
21
#include <basegfx/point/b2dpoint.hxx>
22
#include <basegfx/range/b2drange.hxx>
23
#include <basegfx/matrix/b2dhommatrixtools.hxx>
24
#include <osl/endian.h>
25
26
#include <algorithm>
27
#include <cmath>
28
29
namespace basegfx
30
{
31
    bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
32
0
    {
33
0
        return getTextureTransform() == rODFGradientInfo.getTextureTransform()
34
0
            && getAspectRatio() == rODFGradientInfo.getAspectRatio()
35
0
            && getRequestedSteps() == rODFGradientInfo.getRequestedSteps();
36
0
    }
37
38
    const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const
39
0
    {
40
0
        if(maBackTextureTransform.isIdentity())
41
0
        {
42
0
            const_cast< ODFGradientInfo* >(this)->maBackTextureTransform = getTextureTransform();
43
0
            const_cast< ODFGradientInfo* >(this)->maBackTextureTransform.invert();
44
0
        }
45
46
0
        return maBackTextureTransform;
47
0
    }
48
49
    /** Most of the setup for linear & axial gradient is the same, except
50
        for the border treatment. Factored out here.
51
    */
52
    static ODFGradientInfo init1DGradientInfo(
53
        const B2DRange& rTargetRange,
54
        sal_uInt32 nSteps,
55
        double fBorder,
56
        double fAngle,
57
        bool bAxial)
58
0
    {
59
0
        B2DHomMatrix aTextureTransform;
60
61
0
        fAngle = -fAngle;
62
63
0
        double fTargetSizeX(rTargetRange.getWidth());
64
0
        double fTargetSizeY(rTargetRange.getHeight());
65
0
        double fTargetOffsetX(rTargetRange.getMinX());
66
0
        double fTargetOffsetY(rTargetRange.getMinY());
67
68
        // add object expansion
69
0
        const bool bAngleUsed(!fTools::equalZero(fAngle));
70
71
0
        if(bAngleUsed)
72
0
        {
73
0
            const double fAbsCos(fabs(cos(fAngle)));
74
0
            const double fAbsSin(fabs(sin(fAngle)));
75
0
            const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
76
0
            const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
77
78
0
            fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
79
0
            fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
80
0
            fTargetSizeX = fNewX;
81
0
            fTargetSizeY = fNewY;
82
0
        }
83
84
0
        const double fSizeWithoutBorder(1.0 - fBorder);
85
86
0
        if(bAxial)
87
0
        {
88
0
            aTextureTransform.scale(1.0, fSizeWithoutBorder * 0.5);
89
0
            aTextureTransform.translate(0.0, 0.5);
90
0
        }
91
0
        else
92
0
        {
93
0
            if(!fTools::equal(fSizeWithoutBorder, 1.0))
94
0
            {
95
0
                aTextureTransform.scale(1.0, fSizeWithoutBorder);
96
0
                aTextureTransform.translate(0.0, fBorder);
97
0
            }
98
0
        }
99
100
0
        aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
101
102
        // add texture rotate after scale to keep perpendicular angles
103
0
        if(bAngleUsed)
104
0
        {
105
0
            const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
106
107
0
            aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
108
0
        }
109
110
        // add object translate
111
0
        aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
112
113
        // prepare aspect for texture
114
0
        const double fAspectRatio(fTools::equalZero(fTargetSizeY) ?  1.0 : fTargetSizeX / fTargetSizeY);
115
116
0
        return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
117
0
    }
118
119
    /** Most of the setup for radial & ellipsoidal gradient is the same,
120
        except for the border treatment. Factored out here.
121
    */
122
    static ODFGradientInfo initEllipticalGradientInfo(
123
        const B2DRange& rTargetRange,
124
        const B2DVector& rOffset,
125
        sal_uInt32 nSteps,
126
        double fBorder,
127
        double fAngle,
128
        bool bCircular)
129
0
    {
130
0
        B2DHomMatrix aTextureTransform;
131
132
0
        fAngle = -fAngle;
133
134
0
        double fTargetSizeX(rTargetRange.getWidth());
135
0
        double fTargetSizeY(rTargetRange.getHeight());
136
0
        double fTargetOffsetX(rTargetRange.getMinX());
137
0
        double fTargetOffsetY(rTargetRange.getMinY());
138
139
        // add object expansion
140
0
        if(bCircular)
141
0
        {
142
0
            const double fOriginalDiag(std::hypot(fTargetSizeX, fTargetSizeY));
143
144
0
            fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0;
145
0
            fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0;
146
0
            fTargetSizeX = fOriginalDiag;
147
0
            fTargetSizeY = fOriginalDiag;
148
0
        }
149
0
        else
150
0
        {
151
0
            fTargetOffsetX -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeX;
152
0
            fTargetOffsetY -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeY;
153
0
            fTargetSizeX = M_SQRT2 * fTargetSizeX;
154
0
            fTargetSizeY = M_SQRT2 * fTargetSizeY;
155
0
        }
156
157
0
        const double fHalfBorder((1.0 - fBorder) * 0.5);
158
159
0
        aTextureTransform.scale(fHalfBorder, fHalfBorder);
160
0
        aTextureTransform.translate(0.5, 0.5);
161
0
        aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
162
163
        // add texture rotate after scale to keep perpendicular angles
164
0
        if(!bCircular && !fTools::equalZero(fAngle))
165
0
        {
166
0
            const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
167
168
0
            aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
169
0
        }
170
171
        // add defined offsets after rotation
172
0
        if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
173
0
        {
174
            // use original target size
175
0
            fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
176
0
            fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
177
0
        }
178
179
        // add object translate
180
0
        aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
181
182
        // prepare aspect for texture
183
0
        const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
184
185
0
        return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
186
0
    }
187
188
    /** Setup for rect & square gradient is exactly the same. Factored out
189
        here.
190
    */
191
    static ODFGradientInfo initRectGradientInfo(
192
        const B2DRange& rTargetRange,
193
        const B2DVector& rOffset,
194
        sal_uInt32 nSteps,
195
        double fBorder,
196
        double fAngle,
197
        bool bSquare)
198
0
    {
199
0
        B2DHomMatrix aTextureTransform;
200
201
0
        fAngle = -fAngle;
202
203
0
        double fTargetSizeX(rTargetRange.getWidth());
204
0
        double fTargetSizeY(rTargetRange.getHeight());
205
0
        double fTargetOffsetX(rTargetRange.getMinX());
206
0
        double fTargetOffsetY(rTargetRange.getMinY());
207
208
        // add object expansion
209
0
        if(bSquare)
210
0
        {
211
0
            const double fSquareWidth(std::max(fTargetSizeX, fTargetSizeY));
212
213
0
            fTargetOffsetX -= (fSquareWidth - fTargetSizeX) / 2.0;
214
0
            fTargetOffsetY -= (fSquareWidth - fTargetSizeY) / 2.0;
215
0
            fTargetSizeX = fTargetSizeY = fSquareWidth;
216
0
        }
217
218
        // add object expansion
219
0
        const bool bAngleUsed(!fTools::equalZero(fAngle));
220
221
0
        if(bAngleUsed)
222
0
        {
223
0
            const double fAbsCos(fabs(cos(fAngle)));
224
0
            const double fAbsSin(fabs(sin(fAngle)));
225
0
            const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
226
0
            const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
227
228
0
            fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
229
0
            fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
230
0
            fTargetSizeX = fNewX;
231
0
            fTargetSizeY = fNewY;
232
0
        }
233
234
0
        const double fHalfBorder((1.0 - fBorder) * 0.5);
235
236
0
        aTextureTransform.scale(fHalfBorder, fHalfBorder);
237
0
        aTextureTransform.translate(0.5, 0.5);
238
0
        aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
239
240
        // add texture rotate after scale to keep perpendicular angles
241
0
        if(bAngleUsed)
242
0
        {
243
0
            const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
244
245
0
            aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
246
0
        }
247
248
        // add defined offsets after rotation
249
0
        if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
250
0
        {
251
            // use original target size
252
0
            fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
253
0
            fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
254
0
        }
255
256
        // add object translate
257
0
        aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
258
259
        // prepare aspect for texture
260
0
        const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
261
262
0
        return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
263
0
    }
264
265
    namespace utils
266
    {
267
        /* Tooling method to extract data from given BGradient
268
           to ColorStops, doing some corrections, partially based
269
           on given SingleColor */
270
        void prepareColorStops(
271
            const basegfx::BGradient& rGradient,
272
            BColorStops& rColorStops,
273
            BColor& rSingleColor)
274
0
        {
275
0
            if (rGradient.GetColorStops().isSingleColor(rSingleColor))
276
0
            {
277
                // when single color, preserve value in rSingleColor
278
                // and clear the ColorStops, done.
279
0
                rColorStops.clear();
280
0
                return;
281
0
            }
282
283
0
            const bool bAdaptStartEndIntensity(100 != rGradient.GetStartIntens() || 100 != rGradient.GetEndIntens());
284
0
            const bool bAdaptBorder(0 != rGradient.GetBorder());
285
286
0
            if (!bAdaptStartEndIntensity && !bAdaptBorder)
287
0
            {
288
                // copy unchanged ColorStops & done
289
0
                rColorStops = rGradient.GetColorStops();
290
0
                return;
291
0
            }
292
293
            // prepare a copy to work on
294
0
            basegfx::BGradient aWorkCopy(rGradient);
295
296
0
            if (bAdaptStartEndIntensity)
297
0
            {
298
0
                aWorkCopy.tryToApplyStartEndIntensity();
299
300
                // this can again lead to single color (e.g. both zero, so
301
                // all black), so check again for it
302
0
                if (aWorkCopy.GetColorStops().isSingleColor(rSingleColor))
303
0
                {
304
0
                    rColorStops.clear();
305
0
                    return;
306
0
                }
307
0
            }
308
309
0
            if (bAdaptBorder)
310
0
            {
311
0
                aWorkCopy.tryToApplyBorder();
312
0
            }
313
314
            // extract ColorStops, that's all we need here
315
0
            rColorStops = aWorkCopy.GetColorStops();
316
0
        }
317
318
        /* Tooling method to synchronize the given ColorStops.
319
           The intention is that a color GradientStops and an
320
           alpha/transparence GradientStops gets synchronized
321
           for export. */
322
        void synchronizeColorStops(
323
            BColorStops& rColorStops,
324
            BColorStops& rAlphaStops,
325
            const BColor& rSingleColor,
326
            const BColor& rSingleAlpha)
327
0
        {
328
0
            if (rColorStops.empty())
329
0
            {
330
0
                if (rAlphaStops.empty())
331
0
                {
332
                    // no AlphaStops and no ColorStops
333
                    // create two-stop fallbacks for both
334
0
                    rColorStops = BColorStops {
335
0
                        BColorStop(0.0, rSingleColor),
336
0
                        BColorStop(1.0, rSingleColor) };
337
0
                    rAlphaStops = BColorStops {
338
0
                        BColorStop(0.0, rSingleAlpha),
339
0
                        BColorStop(1.0, rSingleAlpha) };
340
0
                }
341
0
                else
342
0
                {
343
                    // AlphaStops but no ColorStops
344
                    // create fallback synched with existing AlphaStops
345
0
                    for (const auto& cand : rAlphaStops)
346
0
                    {
347
0
                        rColorStops.addStop(cand.getStopOffset(), rSingleColor);
348
0
                    }
349
0
                }
350
351
                // preparations complete, we are done
352
0
                return;
353
0
            }
354
0
            else if (rAlphaStops.empty())
355
0
            {
356
                // ColorStops but no AlphaStops
357
                // create fallback AlphaStops synched with existing ColorStops using SingleAlpha
358
0
                for (const auto& cand : rColorStops)
359
0
                {
360
0
                    rAlphaStops.addStop(cand.getStopOffset(), rSingleAlpha);
361
0
                }
362
363
                // preparations complete, we are done
364
0
                return;
365
0
            }
366
367
            // here we have ColorStops and AlphaStops not empty. Check if we need to
368
            // synchronize both or if they are already usable/in a synched state so
369
            // that they have same count and same StopOffsets
370
0
            bool bNeedToSyncronize(rColorStops.size() != rAlphaStops.size());
371
372
0
            if (!bNeedToSyncronize)
373
0
            {
374
                // check for same StopOffsets
375
0
                BColorStops::const_iterator aCurrColor(rColorStops.begin());
376
0
                BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin());
377
378
0
                while (!bNeedToSyncronize &&
379
0
                    aCurrColor != rColorStops.end() &&
380
0
                    aCurrAlpha != rAlphaStops.end())
381
0
                {
382
0
                    if (fTools::equal(aCurrColor->getStopOffset(), aCurrAlpha->getStopOffset()))
383
0
                    {
384
0
                        aCurrColor++;
385
0
                        aCurrAlpha++;
386
0
                    }
387
0
                    else
388
0
                    {
389
0
                        bNeedToSyncronize = true;
390
0
                    }
391
0
                }
392
0
            }
393
394
0
            if (bNeedToSyncronize)
395
0
            {
396
                // synchronize sizes & StopOffsets
397
0
                BColorStops::const_iterator aCurrColor(rColorStops.begin());
398
0
                BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin());
399
0
                BColorStops aNewColor;
400
0
                BColorStops aNewAlpha;
401
0
                BColorStops::BColorStopRange aColorStopRange;
402
0
                BColorStops::BColorStopRange aAlphaStopRange;
403
0
                bool bRealChange(false);
404
405
0
                do {
406
0
                    const bool bColor(aCurrColor != rColorStops.end());
407
0
                    const bool bAlpha(aCurrAlpha != rAlphaStops.end());
408
409
0
                    if (bColor && bAlpha)
410
0
                    {
411
0
                        const double fColorOff(aCurrColor->getStopOffset());
412
0
                        const double fAlphaOff(aCurrAlpha->getStopOffset());
413
414
0
                        if (fTools::less(fColorOff, fAlphaOff))
415
0
                        {
416
                            // copy color, create alpha
417
0
                            aNewColor.addStop(fColorOff, aCurrColor->getStopColor());
418
0
                            aNewAlpha.addStop(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange));
419
0
                            bRealChange = true;
420
0
                            aCurrColor++;
421
0
                        }
422
0
                        else if (fTools::more(fColorOff, fAlphaOff))
423
0
                        {
424
                            // copy alpha, create color
425
0
                            aNewColor.addStop(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange));
426
0
                            aNewAlpha.addStop(fAlphaOff, aCurrAlpha->getStopColor());
427
0
                            bRealChange = true;
428
0
                            aCurrAlpha++;
429
0
                        }
430
0
                        else
431
0
                        {
432
                            // equal: copy both, advance
433
0
                            aNewColor.addStop(fColorOff, aCurrColor->getStopColor());
434
0
                            aNewAlpha.addStop(fAlphaOff, aCurrAlpha->getStopColor());
435
0
                            aCurrColor++;
436
0
                            aCurrAlpha++;
437
0
                        }
438
0
                    }
439
0
                    else if (bColor)
440
0
                    {
441
0
                        const double fColorOff(aCurrColor->getStopOffset());
442
0
                        aNewAlpha.addStop(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange));
443
0
                        aNewColor.addStop(fColorOff, aCurrColor->getStopColor());
444
0
                        bRealChange = true;
445
0
                        aCurrColor++;
446
0
                    }
447
0
                    else if (bAlpha)
448
0
                    {
449
0
                        const double fAlphaOff(aCurrAlpha->getStopOffset());
450
0
                        aNewColor.addStop(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange));
451
0
                        aNewAlpha.addStop(fAlphaOff, aCurrAlpha->getStopColor());
452
0
                        bRealChange = true;
453
0
                        aCurrAlpha++;
454
0
                    }
455
0
                    else
456
0
                    {
457
                        // no more input, break do..while loop
458
0
                        break;
459
0
                    }
460
0
                }
461
0
                while(true);
462
463
0
                if (bRealChange)
464
0
                {
465
                    // copy on 'real' change, that means data was added.
466
                    // This should always be the cease and should have been
467
                    // detected as such above, see bNeedToSyncronize
468
0
                    rColorStops = std::move(aNewColor);
469
0
                    rAlphaStops = std::move(aNewAlpha); // MCGR: tdf#155537 used wrong result here
470
0
                }
471
0
            }
472
0
        }
473
474
        sal_uInt32 calculateNumberOfSteps(
475
            sal_uInt32 nRequestedSteps,
476
            const BColor& rStart,
477
            const BColor& rEnd)
478
0
        {
479
0
            const sal_uInt32 nMaxSteps(sal_uInt32((rStart.getMaximumDistance(rEnd) * 127.5) + 0.5));
480
481
0
            if (0 == nRequestedSteps)
482
0
            {
483
0
                nRequestedSteps = nMaxSteps;
484
0
            }
485
486
0
            if(nRequestedSteps > nMaxSteps)
487
0
            {
488
0
                nRequestedSteps = nMaxSteps;
489
0
            }
490
491
0
            return std::max(sal_uInt32(1), nRequestedSteps);
492
0
        }
493
494
        ODFGradientInfo createLinearODFGradientInfo(
495
            const B2DRange& rTargetArea,
496
            sal_uInt32 nSteps,
497
            double fBorder,
498
            double fAngle)
499
0
        {
500
0
            return init1DGradientInfo(
501
0
                rTargetArea,
502
0
                nSteps,
503
0
                fBorder,
504
0
                fAngle,
505
0
                false);
506
0
        }
507
508
        ODFGradientInfo createAxialODFGradientInfo(
509
            const B2DRange& rTargetArea,
510
            sal_uInt32 nSteps,
511
            double fBorder,
512
            double fAngle)
513
0
        {
514
0
            return init1DGradientInfo(
515
0
                rTargetArea,
516
0
                nSteps,
517
0
                fBorder,
518
0
                fAngle,
519
0
                true);
520
0
        }
521
522
        ODFGradientInfo createRadialODFGradientInfo(
523
            const B2DRange& rTargetArea,
524
            const B2DVector& rOffset,
525
            sal_uInt32 nSteps,
526
            double fBorder)
527
0
        {
528
0
            return initEllipticalGradientInfo(
529
0
                rTargetArea,
530
0
                rOffset,
531
0
                nSteps,
532
0
                fBorder,
533
0
                0.0,
534
0
                true);
535
0
        }
536
537
        ODFGradientInfo createEllipticalODFGradientInfo(
538
            const B2DRange& rTargetArea,
539
            const B2DVector& rOffset,
540
            sal_uInt32 nSteps,
541
            double fBorder,
542
            double fAngle)
543
0
        {
544
0
            return initEllipticalGradientInfo(
545
0
                rTargetArea,
546
0
                rOffset,
547
0
                nSteps,
548
0
                fBorder,
549
0
                fAngle,
550
0
                false);
551
0
        }
552
553
        ODFGradientInfo createSquareODFGradientInfo(
554
            const B2DRange& rTargetArea,
555
            const B2DVector& rOffset,
556
            sal_uInt32 nSteps,
557
            double fBorder,
558
            double fAngle)
559
0
        {
560
0
            return initRectGradientInfo(
561
0
                rTargetArea,
562
0
                rOffset,
563
0
                nSteps,
564
0
                fBorder,
565
0
                fAngle,
566
0
                true);
567
0
        }
568
569
        ODFGradientInfo createRectangularODFGradientInfo(
570
            const B2DRange& rTargetArea,
571
            const B2DVector& rOffset,
572
            sal_uInt32 nSteps,
573
            double fBorder,
574
            double fAngle)
575
0
        {
576
0
            return initRectGradientInfo(
577
0
                rTargetArea,
578
0
                rOffset,
579
0
                nSteps,
580
0
                fBorder,
581
0
                fAngle,
582
0
                false);
583
0
        }
584
585
        double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
586
0
        {
587
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
588
589
            // Ignore X, this is not needed at all for Y-Oriented gradients
590
            // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
591
            // {
592
            //     return 0.0;
593
            // }
594
595
0
            if(aCoor.getY() <= 0.0)
596
0
            {
597
0
                return 0.0; // start value for inside
598
0
            }
599
600
0
            if(aCoor.getY() >= 1.0)
601
0
            {
602
0
                return 1.0; // end value for outside
603
0
            }
604
605
0
            return aCoor.getY();
606
0
        }
607
608
        double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
609
0
        {
610
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
611
612
            // Ignore X, this is not needed at all for Y-Oriented gradients
613
            //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
614
            //{
615
            //    return 0.0;
616
            //}
617
618
0
            const double fAbsY(fabs(aCoor.getY()));
619
620
0
            if(fAbsY >= 1.0)
621
0
            {
622
0
                return 1.0; // use end value when outside in Y
623
0
            }
624
625
0
            return fAbsY;
626
0
        }
627
628
        double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
629
0
        {
630
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
631
632
0
            if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
633
0
            {
634
0
                return 0.0;
635
0
            }
636
637
0
            return 1.0 - std::hypot(aCoor.getX(), aCoor.getY());
638
0
        }
639
640
        double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
641
0
        {
642
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
643
644
0
            if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
645
0
            {
646
0
                return 0.0;
647
0
            }
648
649
0
            double fAspectRatio(rGradInfo.getAspectRatio());
650
0
            double t(1.0);
651
652
            // MCGR: Similar to getRectangularGradientAlpha (please
653
            // see there) we need to use aspect ratio here. Due to
654
            // initEllipticalGradientInfo using M_SQRT2 to make this
655
            // gradient look 'nicer' this correction seems not 100%
656
            // correct, but is close enough for now
657
0
            if(fAspectRatio > 1.0)
658
0
            {
659
0
                t = 1.0 - std::hypot(aCoor.getX() / fAspectRatio, aCoor.getY());
660
0
            }
661
0
            else if(fAspectRatio > 0.0)
662
0
            {
663
0
                t = 1.0 - std::hypot(aCoor.getX(), aCoor.getY() * fAspectRatio);
664
0
            }
665
666
0
            return t;
667
0
        }
668
669
        double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
670
0
        {
671
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
672
0
            const double fAbsX(fabs(aCoor.getX()));
673
674
0
            if(fAbsX >= 1.0)
675
0
            {
676
0
                return 0.0;
677
0
            }
678
679
0
            const double fAbsY(fabs(aCoor.getY()));
680
681
0
            if(fAbsY >= 1.0)
682
0
            {
683
0
                return 0.0;
684
0
            }
685
686
0
            return 1.0 - std::max(fAbsX, fAbsY);
687
0
        }
688
689
        double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
690
0
        {
691
0
            const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
692
0
            double fAbsX(fabs(aCoor.getX()));
693
694
0
            if(fAbsX >= 1.0)
695
0
            {
696
0
                return 0.0;
697
0
            }
698
699
0
            double fAbsY(fabs(aCoor.getY()));
700
701
0
            if(fAbsY >= 1.0)
702
0
            {
703
0
                return 0.0;
704
0
            }
705
706
            // MCGR: Visualizations using the texturing method for
707
            // displaying gradients (getBackTextureTransform is
708
            // involved) show wrong results for GradientElliptical
709
            // and GradientRect, this can be best seen when using
710
            // less steps, e.g. just four. This thus has influence
711
            // on cppcanvas (slideshow) and 3D textures, so needs
712
            // to be corrected.
713
            // Missing is to use the aspect ratio of the object
714
            // in this [-1, -1, 1, 1] unified coordinate space
715
            // after getBackTextureTransform is applied. Optically
716
            // in the larger direction of the texturing the color
717
            // step distances are too big *because* we are in that
718
            // unit range now.
719
            // To correct that, a kind of 'limo stretching' needs to
720
            // be applied, adding space around the center
721
            // proportional to the aspect ratio, so the intuitive
722
            // idea would be to do
723
            //
724
            // fAbsX' = ((fAspectRatio - 1) + fAbsX) / fAspectRatio
725
            //
726
            // which scales from the center. This does not work, and
727
            // after some thoughts it's clear why: It's not the
728
            // position that needs to be moved (this cannot be
729
            // changed), but the position *before* that scale has
730
            // to be determined to get the correct, shifted color
731
            // for the already 'new' position. Thus, turn around
732
            // the expression as
733
            //
734
            // fAbsX' * fAspectRatio = fAspectRatio - 1 + fAbsX
735
            // fAbsX' * fAspectRatio - fAspectRatio + 1 = fAbsX
736
            // fAbsX = (fAbsX' - 1) * fAspectRatio + 1
737
            //
738
            // This works and can even be simply adapted for
739
            // fAspectRatio < 1.0 aka vertical is bigger.
740
0
            double fAspectRatio(rGradInfo.getAspectRatio());
741
0
            if(fAspectRatio > 1.0)
742
0
            {
743
0
                fAbsX = ((fAbsX - 1) * fAspectRatio) + 1;
744
0
            }
745
0
            else if(fAspectRatio > 0.0)
746
0
            {
747
0
                fAbsY = ((fAbsY - 1) / fAspectRatio) + 1;
748
0
            }
749
750
0
            return 1.0 - std::max(fAbsX, fAbsY);
751
0
        }
752
    } // namespace utils
753
} // namespace basegfx
754
755
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */