Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/canvas/source/vcl/canvashelper_texturefill.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 <cstdlib>
23
#include <tuple>
24
25
#include <basegfx/matrix/b2dhommatrix.hxx>
26
#include <basegfx/numeric/ftools.hxx>
27
#include <basegfx/point/b2dpoint.hxx>
28
#include <basegfx/polygon/b2dpolygon.hxx>
29
#include <basegfx/polygon/b2dpolygontools.hxx>
30
#include <basegfx/range/b2drectangle.hxx>
31
#include <basegfx/utils/canvastools.hxx>
32
#include <basegfx/utils/keystoplerp.hxx>
33
#include <basegfx/utils/lerp.hxx>
34
#include <basegfx/utils/tools.hxx>
35
#include <com/sun/star/awt/GradientStyle.hpp>
36
#include <com/sun/star/rendering/TexturingMode.hpp>
37
#include <rtl/math.hxx>
38
#include <comphelper/diagnose_ex.hxx>
39
#include <tools/poly.hxx>
40
#include <vcl/alpha.hxx>
41
#include <vcl/bitmap.hxx>
42
#include <vcl/canvastools.hxx>
43
#include <vcl/virdev.hxx>
44
#include <vcl/gradient.hxx>
45
46
#include <canvas/canvastools.hxx>
47
#include <parametricpolypolygon.hxx>
48
49
#include "canvashelper.hxx"
50
#include "impltools.hxx"
51
52
53
using namespace ::com::sun::star;
54
55
namespace vclcanvas
56
{
57
    namespace
58
    {
59
        bool textureFill( OutputDevice&         rOutDev,
60
                          const GraphicObject&  rGraphic,
61
                          const ::Point&        rPosPixel,
62
                          const ::Size&         rNextTileX,
63
                          const ::Size&         rNextTileY,
64
                          sal_Int32             nTilesX,
65
                          sal_Int32             nTilesY,
66
                          const ::Size&         rTileSize,
67
                          const GraphicAttr&    rAttr)
68
0
        {
69
0
            bool bRet( false );
70
0
            Point   aCurrPos;
71
0
            int     nX, nY;
72
73
0
            for( nY=0; nY < nTilesY; ++nY )
74
0
            {
75
0
                aCurrPos.setX( rPosPixel.X() + nY*rNextTileY.Width() );
76
0
                aCurrPos.setY( rPosPixel.Y() + nY*rNextTileY.Height() );
77
78
0
                for( nX=0; nX < nTilesX; ++nX )
79
0
                {
80
                    // update return value. This method should return true, if
81
                    // at least one of the looped Draws succeeded.
82
0
                    bRet |= rGraphic.Draw(rOutDev,
83
0
                                          aCurrPos,
84
0
                                          rTileSize,
85
0
                                          &rAttr);
86
87
0
                    aCurrPos.AdjustX(rNextTileX.Width() );
88
0
                    aCurrPos.AdjustY(rNextTileX.Height() );
89
0
                }
90
0
            }
91
92
0
            return bRet;
93
0
        }
94
95
96
        /** Fill linear or axial gradient
97
98
            Since most of the code for linear and axial gradients are
99
            the same, we've a unified method here
100
         */
101
        void fillLinearGradient( OutputDevice&                                  rOutDev,
102
                                 const ::basegfx::B2DHomMatrix&                 rTextureTransform,
103
                                 const ::tools::Rectangle&                             rBounds,
104
                                 unsigned int                                   nStepCount,
105
                                 const ::canvas::ParametricPolyPolygon::Values& rValues,
106
                                 const std::vector< ::Color >&                  rColors )
107
0
        {
108
            // determine general position of gradient in relation to
109
            // the bound rect
110
            // =====================================================
111
112
0
            ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
113
0
            ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
114
0
            ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
115
0
            ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
116
117
0
            aLeftTop    *= rTextureTransform;
118
0
            aLeftBottom *= rTextureTransform;
119
0
            aRightTop   *= rTextureTransform;
120
0
            aRightBottom*= rTextureTransform;
121
122
            // calc length of bound rect diagonal
123
0
            const ::basegfx::B2DVector aBoundRectDiagonal(
124
0
                vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) -
125
0
                vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) );
126
0
            const double nDiagonalLength( aBoundRectDiagonal.getLength() );
127
128
            // create direction of gradient:
129
            //     _______
130
            //     |  |  |
131
            // ->  |  |  | ...
132
            //     |  |  |
133
            //     -------
134
0
            ::basegfx::B2DVector aDirection( aRightTop - aLeftTop );
135
0
            aDirection.normalize();
136
137
            // now, we potentially have to enlarge our gradient area
138
            // atop and below the transformed [0,1]x[0,1] unit rect,
139
            // for the gradient to fill the complete bound rect.
140
0
            ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop,
141
0
                                                             aLeftBottom,
142
0
                                                             aRightTop,
143
0
                                                             aRightBottom,
144
0
                                                             vcl::unotools::b2DRectangleFromRectangle(rBounds) );
145
146
147
            // render gradient
148
            // ===============
149
150
            // First try to use directly VCL's DrawGradient(), as that one is generally
151
            // a better choice than here decomposing to polygons. The VCL API allows
152
            // only 2 colors, but that should generally do.
153
            // Do not use nStepCount, it limits optimized implementations, and it's computed
154
            // by vclcanvas based on number of colors, so it's practically irrelevant.
155
156
            // 2 colors and 2 stops (at 0 and 1) is a linear gradient:
157
0
            if( rColors.size() == 2 && rValues.maStops.size() == 2 && rValues.maStops[0] == 0 && rValues.maStops[1] == 1)
158
0
            {
159
                // tdf#144073 and tdf#147645: use bounds and angle for gradient
160
                // Passing an expanded, rotated polygon noticeably modifies the
161
                // drawing of the gradient in a slideshow due to moving of the
162
                // starting and ending colors far off the edges of the drawing
163
                // surface. So try another way and set the angle of the
164
                // gradient and draw only the unadjusted bounds.
165
0
                Gradient vclGradient( css::awt::GradientStyle_LINEAR, rColors[ 0 ], rColors[ 1 ] );
166
0
                double fRotate = atan2( aDirection.getY(), aDirection.getX() );
167
0
                const double nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0;
168
0
                vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) );
169
0
                rOutDev.DrawGradient( rBounds, vclGradient );
170
0
                return;
171
0
            }
172
            // 3 colors with first and last being equal and 3 stops (at 0, 0.5 and 1) is an axial gradient:
173
0
            if( rColors.size() == 3 && rColors[ 0 ] == rColors[ 2 ]
174
0
                && rValues.maStops.size() == 3 && rValues.maStops[0] == 0
175
0
                && rValues.maStops[1] == 0.5 && rValues.maStops[2] == 1)
176
0
            {
177
                // tdf#144073 and tdf#147645: use bounds and angle for gradient
178
                // Passing an expanded, rotated polygon noticeably modifies the
179
                // drawing of the gradient in a slideshow due to moving of the
180
                // starting and ending colors far off the edges of the drawing
181
                // surface. So try another way and set the angle of the
182
                // gradient and draw only the unadjusted bounds.
183
0
                Gradient vclGradient( css::awt::GradientStyle_AXIAL, rColors[ 1 ], rColors[ 0 ] );
184
0
                double fRotate = atan2( aDirection.getY(), aDirection.getX() );
185
0
                const double nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0;
186
0
                vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) );
187
0
                rOutDev.DrawGradient( rBounds, vclGradient );
188
0
                return;
189
0
            }
190
191
            // for linear gradients, it's easy to render
192
            // non-overlapping polygons: just split the gradient into
193
            // nStepCount small strips. Prepare the strip now.
194
195
            // For performance reasons, we create a temporary VCL
196
            // polygon here, keep it all the way and only change the
197
            // vertex values in the loop below (as ::Polygon is a
198
            // pimpl class, creating one every loop turn would really
199
            // stress the mem allocator)
200
0
            ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
201
202
0
            OSL_ENSURE( nStepCount >= 3,
203
0
                        "fillLinearGradient(): stepcount smaller than 3" );
204
205
206
            // fill initial strip (extending two times the bound rect's
207
            // diagonal to the 'left'
208
209
210
            // calculate left edge, by moving left edge of the
211
            // gradient rect two times the bound rect's diagonal to
212
            // the 'left'. Since we postpone actual rendering into the
213
            // loop below, we set the _right_ edge here, which will be
214
            // readily copied into the left edge in the loop below
215
0
            const ::basegfx::B2DPoint aPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
216
0
            aTempPoly[1] = ::Point( ::basegfx::fround<::tools::Long>( aPoint1.getX() ),
217
0
                                    ::basegfx::fround<::tools::Long>( aPoint1.getY() ) );
218
219
0
            const ::basegfx::B2DPoint aPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection );
220
0
            aTempPoly[2] = ::Point( ::basegfx::fround<::tools::Long>( aPoint2.getX() ),
221
0
                                    ::basegfx::fround<::tools::Long>( aPoint2.getY() ) );
222
223
224
            // iteratively render all other strips
225
226
227
            // ensure that nStepCount matches color stop parity, to
228
            // have a well-defined middle color e.g. for axial
229
            // gradients.
230
0
            if( (rColors.size() % 2) != (nStepCount % 2) )
231
0
                ++nStepCount;
232
233
0
            rOutDev.SetLineColor();
234
235
0
            basegfx::utils::KeyStopLerp aLerper(rValues.maStops);
236
237
            // only iterate nStepCount-1 steps, as the last strip is
238
            // explicitly painted below
239
0
            for( unsigned int i=0; i<nStepCount-1; ++i )
240
0
            {
241
0
                std::ptrdiff_t nIndex;
242
0
                double fAlpha;
243
0
                std::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount);
244
245
0
                rOutDev.SetFillColor(
246
0
                    Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
247
0
                           static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
248
0
                           static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
249
250
                // copy right edge of polygon to left edge (and also
251
                // copy the closing point)
252
0
                aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
253
0
                aTempPoly[3] = aTempPoly[2];
254
255
                // calculate new right edge, from interpolating
256
                // between start and end line. Note that i is
257
                // increased by one, to account for the fact that we
258
                // calculate the right border here (whereas the fill
259
                // color is governed by the left edge)
260
0
                const ::basegfx::B2DPoint aPoint3(
261
0
                    (nStepCount - i-1)/double(nStepCount)*aLeftTop +
262
0
                    (i+1)/double(nStepCount)*aRightTop );
263
0
                aTempPoly[1] = ::Point( ::basegfx::fround<::tools::Long>( aPoint3.getX() ),
264
0
                                        ::basegfx::fround<::tools::Long>( aPoint3.getY() ) );
265
266
0
                const ::basegfx::B2DPoint aPoint4(
267
0
                    (nStepCount - i-1)/double(nStepCount)*aLeftBottom +
268
0
                    (i+1)/double(nStepCount)*aRightBottom );
269
0
                aTempPoly[2] = ::Point( ::basegfx::fround<::tools::Long>( aPoint4.getX() ),
270
0
                                        ::basegfx::fround<::tools::Long>( aPoint4.getY() ) );
271
272
0
                rOutDev.DrawPolygon( aTempPoly );
273
0
            }
274
275
            // fill final strip (extending two times the bound rect's
276
            // diagonal to the 'right'
277
278
279
            // copy right edge of polygon to left edge (and also
280
            // copy the closing point)
281
0
            aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
282
0
            aTempPoly[3] = aTempPoly[2];
283
284
            // calculate new right edge, by moving right edge of the
285
            // gradient rect two times the bound rect's diagonal to
286
            // the 'right'.
287
0
            const ::basegfx::B2DPoint aPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
288
0
            aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround<::tools::Long>( aPoint3.getX() ),
289
0
                                                   ::basegfx::fround<::tools::Long>( aPoint3.getY() ) );
290
291
0
            const ::basegfx::B2DPoint aPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection );
292
0
            aTempPoly[3] = ::Point( ::basegfx::fround<::tools::Long>( aPoint4.getX() ),
293
0
                                    ::basegfx::fround<::tools::Long>( aPoint4.getY() ) );
294
295
0
            rOutDev.SetFillColor( rColors.back() );
296
297
0
            rOutDev.DrawPolygon( aTempPoly );
298
0
        }
299
300
        void fillPolygonalGradient( OutputDevice&                                  rOutDev,
301
                                    const ::basegfx::B2DHomMatrix&                 rTextureTransform,
302
                                    const ::tools::Rectangle&                             rBounds,
303
                                    unsigned int                                   nStepCount,
304
                                    const ::canvas::ParametricPolyPolygon::Values& rValues,
305
                                    const std::vector< ::Color >&                  rColors )
306
0
        {
307
0
            const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
308
309
0
            ENSURE_OR_THROW( rGradientPoly.count() > 2,
310
0
                              "fillPolygonalGradient(): polygon without area given" );
311
312
            // For performance reasons, we create a temporary VCL polygon
313
            // here, keep it all the way and only change the vertex values
314
            // in the loop below (as ::Polygon is a pimpl class, creating
315
            // one every loop turn would really stress the mem allocator)
316
0
            ::basegfx::B2DPolygon   aOuterPoly( rGradientPoly );
317
0
            ::basegfx::B2DPolygon   aInnerPoly;
318
319
            // subdivide polygon _before_ rendering, would otherwise have
320
            // to be performed on every loop turn.
321
0
            if( aOuterPoly.areControlPointsUsed() )
322
0
                aOuterPoly = ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly);
323
324
0
            aInnerPoly = aOuterPoly;
325
326
            // only transform outer polygon _after_ copying it into
327
            // aInnerPoly, because inner polygon has to be scaled before
328
            // the actual texture transformation takes place
329
0
            aOuterPoly.transform( rTextureTransform );
330
331
            // determine overall transformation for inner polygon (might
332
            // have to be prefixed by anisotropic scaling)
333
0
            ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
334
335
336
            // apply scaling (possibly anisotropic) to inner polygon
337
338
339
            // scale inner polygon according to aspect ratio: for
340
            // wider-than-tall bounds (nAspectRatio > 1.0), the inner
341
            // polygon, representing the gradient focus, must have
342
            // non-zero width. Specifically, a bound rect twice as wide as
343
            // tall has a focus polygon of half its width.
344
0
            const double nAspectRatio( rValues.mnAspectRatio );
345
0
            if( nAspectRatio > 1.0 )
346
0
            {
347
                // width > height case
348
0
                aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
349
0
                                                    0.0 );
350
0
            }
351
0
            else if( nAspectRatio < 1.0 )
352
0
            {
353
                // width < height case
354
0
                aInnerPolygonTransformMatrix.scale( 0.0,
355
0
                                                    1.0 - nAspectRatio );
356
0
            }
357
0
            else
358
0
            {
359
                // isotropic case
360
0
                aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
361
0
            }
362
363
            // and finally, add texture transform to it.
364
0
            aInnerPolygonTransformMatrix *= rTextureTransform;
365
366
            // apply final matrix to polygon
367
0
            aInnerPoly.transform( aInnerPolygonTransformMatrix );
368
369
370
0
            const sal_uInt32 nNumPoints( aOuterPoly.count() );
371
0
            ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(nNumPoints+1) );
372
373
            // increase number of steps by one: polygonal gradients have
374
            // the outermost polygon rendered in rColor2, and the
375
            // innermost in rColor1. The innermost polygon will never
376
            // have zero area, thus, we must divide the interval into
377
            // nStepCount+1 steps. For example, to create 3 steps:
378
379
            // |                       |
380
            // |-------|-------|-------|
381
            // |                       |
382
            // 3       2       1       0
383
384
            // This yields 4 tick marks, where 0 is never attained (since
385
            // zero-area polygons typically don't display perceivable
386
            // color).
387
0
            ++nStepCount;
388
389
0
            rOutDev.SetLineColor();
390
391
0
            basegfx::utils::KeyStopLerp aLerper(rValues.maStops);
392
393
            // fill background
394
0
            rOutDev.SetFillColor( rColors.front() );
395
0
            rOutDev.DrawRect( rBounds );
396
397
            // render polygon
398
            // ==============
399
400
0
            for( unsigned int i=1,p; i<nStepCount; ++i )
401
0
            {
402
0
                const double fT( i/double(nStepCount) );
403
404
0
                std::ptrdiff_t nIndex;
405
0
                double fAlpha;
406
0
                std::tie(nIndex,fAlpha)=aLerper.lerp(fT);
407
408
                // lerp color
409
0
                rOutDev.SetFillColor(
410
0
                    Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
411
0
                           static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
412
0
                           static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
413
414
                // scale and render polygon, by interpolating between
415
                // outer and inner polygon.
416
417
0
                for( p=0; p<nNumPoints; ++p )
418
0
                {
419
0
                    const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
420
0
                    const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
421
422
0
                    aTempPoly[static_cast<sal_uInt16>(p)] = ::Point(
423
0
                        basegfx::fround<::tools::Long>( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
424
0
                        basegfx::fround<::tools::Long>( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
425
0
                }
426
427
                // close polygon explicitly
428
0
                aTempPoly[static_cast<sal_uInt16>(p)] = aTempPoly[0];
429
430
                // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
431
                // OutputDevice::ImplDrawComplexGradient(), there's a note
432
                // that on some VDev's, rendering disjunct poly-polygons
433
                // is faster!
434
0
                rOutDev.DrawPolygon( aTempPoly );
435
0
            }
436
0
        }
437
438
        void doGradientFill( OutputDevice&                                  rOutDev,
439
                             const ::canvas::ParametricPolyPolygon::Values& rValues,
440
                             const std::vector< ::Color >&                  rColors,
441
                             const ::basegfx::B2DHomMatrix&                 rTextureTransform,
442
                             const ::tools::Rectangle&                      rBounds,
443
                             unsigned int                                   nStepCount )
444
0
        {
445
0
            switch( rValues.meType )
446
0
            {
447
0
                case ::canvas::ParametricPolyPolygon::GradientType::Linear:
448
0
                    fillLinearGradient( rOutDev,
449
0
                                        rTextureTransform,
450
0
                                        rBounds,
451
0
                                        nStepCount,
452
0
                                        rValues,
453
0
                                        rColors );
454
0
                    break;
455
456
0
                case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
457
0
                case ::canvas::ParametricPolyPolygon::GradientType::Rectangular:
458
0
                    fillPolygonalGradient( rOutDev,
459
0
                                           rTextureTransform,
460
0
                                           rBounds,
461
0
                                           nStepCount,
462
0
                                           rValues,
463
0
                                           rColors );
464
0
                    break;
465
466
0
                default:
467
0
                    ENSURE_OR_THROW( false,
468
0
                                      "CanvasHelper::doGradientFill(): Unexpected case" );
469
0
            }
470
0
        }
471
472
        int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 )
473
0
        {
474
0
            return std::max(
475
0
                std::abs( rColor1.GetRed() - rColor2.GetRed() ),
476
0
                std::max(
477
0
                    std::abs( rColor1.GetGreen() - rColor2.GetGreen() ),
478
0
                    std::abs( rColor1.GetBlue()  - rColor2.GetBlue() ) ) );
479
0
        }
480
481
        bool gradientFill( OutputDevice&                                   rOutDev,
482
                           OutputDevice*                                   p2ndOutDev,
483
                           const ::canvas::ParametricPolyPolygon::Values&  rValues,
484
                           const std::vector< ::Color >&                   rColors,
485
                           const ::tools::PolyPolygon&                     rPoly,
486
                           const rendering::ViewState&                     viewState,
487
                           const rendering::RenderState&                   renderState,
488
                           const rendering::Texture&                       texture,
489
                           int                                             nTransparency )
490
0
        {
491
            // TODO(T2): It is maybe necessary to lock here, should
492
            // maGradientPoly someday cease to be const. But then, beware of
493
            // deadlocks, canvashelper calls this method with locked own
494
            // mutex.
495
496
            // calc step size
497
498
0
            int nColorSteps = 0;
499
0
            for( size_t i=0; i<rColors.size()-1; ++i )
500
0
                nColorSteps += numColorSteps(rColors[i],rColors[i+1]);
501
502
0
            ::basegfx::B2DHomMatrix aTotalTransform;
503
0
            const int nStepCount=
504
0
                ::canvastools::calcGradientStepCount(aTotalTransform,
505
0
                                                       viewState,
506
0
                                                       renderState,
507
0
                                                       texture,
508
0
                                                       nColorSteps);
509
510
0
            rOutDev.SetLineColor();
511
512
            // determine maximal bound rect of texture-filled
513
            // polygon
514
0
            const ::tools::Rectangle aPolygonDeviceRectOrig(
515
0
                rPoly.GetBoundRect() );
516
517
0
            if( vclcanvastools::isRectangle( rPoly ) )
518
0
            {
519
                // use optimized output path
520
521
522
                // this distinction really looks like a
523
                // micro-optimization, but in fact greatly speeds up
524
                // especially complex gradients. That's because when using
525
                // clipping, we can output polygons instead of
526
                // poly-polygons, and don't have to output the gradient
527
                // twice for XOR
528
529
0
                rOutDev.Push( vcl::PushFlags::CLIPREGION );
530
0
                rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
531
0
                doGradientFill( rOutDev,
532
0
                                rValues,
533
0
                                rColors,
534
0
                                aTotalTransform,
535
0
                                aPolygonDeviceRectOrig,
536
0
                                nStepCount );
537
0
                rOutDev.Pop();
538
539
0
                if( p2ndOutDev && nTransparency < 253 )
540
0
                {
541
                    // HACK. Normally, CanvasHelper does not care about
542
                    // actually what mp2ndOutDev is...  well, here we do &
543
                    // assume a 1bpp target - everything beyond 97%
544
                    // transparency is fully transparent
545
0
                    p2ndOutDev->SetFillColor( COL_BLACK );
546
0
                    p2ndOutDev->DrawRect( aPolygonDeviceRectOrig );
547
0
                }
548
0
            }
549
0
            else
550
0
            {
551
0
                const vcl::Region aPolyClipRegion( rPoly );
552
553
0
                rOutDev.Push( vcl::PushFlags::CLIPREGION );
554
0
                rOutDev.IntersectClipRegion( aPolyClipRegion );
555
556
0
                doGradientFill( rOutDev,
557
0
                                rValues,
558
0
                                rColors,
559
0
                                aTotalTransform,
560
0
                                aPolygonDeviceRectOrig,
561
0
                                nStepCount );
562
0
                rOutDev.Pop();
563
564
0
                if( p2ndOutDev && nTransparency < 253 )
565
0
                {
566
                    // HACK. Normally, CanvasHelper does not care about
567
                    // actually what mp2ndOutDev is...  well, here we do &
568
                    // assume a 1bpp target - everything beyond 97%
569
                    // transparency is fully transparent
570
0
                    p2ndOutDev->SetFillColor( COL_BLACK );
571
0
                    p2ndOutDev->DrawPolyPolygon( rPoly );
572
0
                }
573
0
            }
574
575
#ifdef DEBUG_CANVAS_CANVASHELPER_TEXTUREFILL
576
            // extra-verbosity
577
            {
578
                ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
579
                ::basegfx::B2DHomMatrix aTextureTransform;
580
                ::basegfx::B2DRectangle aTextureDeviceRect = ::canvastools::calcTransformedRectBounds(
581
                                                            aRect,
582
                                                            aTextureTransform );
583
                rOutDev.SetLineColor( COL_RED );
584
                rOutDev.SetFillColor();
585
                rOutDev.DrawRect( vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
586
587
                rOutDev.SetLineColor( COL_BLUE );
588
                ::tools::Polygon aPoly1(
589
                    vcl::unotools::rectangleFromB2DRectangle( aRect ));
590
                ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() );
591
                aPoly2.transform( aTextureTransform );
592
                ::tools::Polygon aPoly3( aPoly2 );
593
                rOutDev.DrawPolygon( aPoly3 );
594
            }
595
#endif
596
597
0
            return true;
598
0
        }
599
    }
600
601
    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas*                          pCanvas,
602
                                                                                         const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
603
                                                                                         const rendering::ViewState&                        viewState,
604
                                                                                         const rendering::RenderState&                      renderState,
605
                                                                                         const uno::Sequence< rendering::Texture >&         textures )
606
0
    {
607
0
        ENSURE_ARG_OR_THROW( xPolyPolygon.is(),
608
0
                         "CanvasHelper::fillPolyPolygon(): polygon is NULL");
609
0
        ENSURE_ARG_OR_THROW( textures.hasElements(),
610
0
                         "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
611
612
0
        if( mpOutDevProvider )
613
0
        {
614
0
            vclcanvastools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider );
615
616
0
            const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) );
617
0
            ::tools::PolyPolygon aPolyPoly( vclcanvastools::mapPolyPolygon(
618
0
                                       ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon),
619
0
                                       viewState, renderState ) );
620
621
            // TODO(F1): Multi-texturing
622
0
            if( textures[0].Gradient.is() )
623
0
            {
624
                // try to cast XParametricPolyPolygon2D reference to
625
                // our implementation class.
626
0
                ::canvas::ParametricPolyPolygon* pGradient =
627
0
                      dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
628
629
0
                if( pGradient && pGradient->getValues().maColors.hasElements() )
630
0
                {
631
                    // copy state from Gradient polypoly locally
632
                    // (given object might change!)
633
0
                    const ::canvas::ParametricPolyPolygon::Values aValues(
634
0
                        pGradient->getValues() );
635
636
0
                    if( aValues.maColors.getLength() < 2 )
637
0
                    {
638
0
                        rendering::RenderState aTempState=renderState;
639
0
                        aTempState.DeviceColor = aValues.maColors[0];
640
0
                        fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState);
641
0
                    }
642
0
                    else
643
0
                    {
644
0
                        std::vector< ::Color > aColors(aValues.maColors.getLength());
645
0
                        std::transform(&aValues.maColors[0],
646
0
                                       &aValues.maColors[0]+aValues.maColors.getLength(),
647
0
                                       aColors.begin(),
648
0
                                       [](const uno::Sequence< double >& aColor) {
649
0
                                           return vcl::unotools::stdColorSpaceSequenceToColor( aColor );
650
0
                                       } );
651
652
                        // TODO(E1): Return value
653
                        // TODO(F1): FillRule
654
0
                        gradientFill( mpOutDevProvider->getOutDev(),
655
0
                                      mp2ndOutDevProvider ? &mp2ndOutDevProvider->getOutDev() : nullptr,
656
0
                                      aValues,
657
0
                                      aColors,
658
0
                                      aPolyPoly,
659
0
                                      viewState,
660
0
                                      renderState,
661
0
                                      textures[0],
662
0
                                      nTransparency );
663
0
                    }
664
0
                }
665
0
                else
666
0
                {
667
                    // TODO(F1): The generic case is missing here
668
0
                    ENSURE_OR_THROW( false,
669
0
                                      "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
670
0
                }
671
0
            }
672
0
            else if( textures[0].Bitmap.is() )
673
0
            {
674
0
                geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() );
675
676
0
                ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
677
0
                                 aBmpSize.Height != 0,
678
0
                                 "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
679
680
                // determine maximal bound rect of texture-filled
681
                // polygon
682
0
                const ::tools::Rectangle aPolygonDeviceRect(
683
0
                    aPolyPoly.GetBoundRect() );
684
685
686
                // first of all, determine whether we have a
687
                // drawBitmap() in disguise
688
                // =========================================
689
690
0
                const bool bRectangularPolygon( vclcanvastools::isRectangle( aPolyPoly ) );
691
692
0
                ::basegfx::B2DHomMatrix aTotalTransform;
693
0
                ::canvastools::mergeViewAndRenderTransform(aTotalTransform,
694
0
                                                             viewState,
695
0
                                                             renderState);
696
0
                ::basegfx::B2DHomMatrix aTextureTransform;
697
0
                ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
698
0
                                                                textures[0].AffineTransform );
699
700
0
                aTotalTransform *= aTextureTransform;
701
702
0
                const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
703
0
                ::basegfx::B2DRectangle aTextureDeviceRect = ::canvastools::calcTransformedRectBounds(
704
0
                                                            aRect,
705
0
                                                            aTotalTransform );
706
707
0
                const ::tools::Rectangle aIntegerTextureDeviceRect(
708
0
                    vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
709
710
0
                if( bRectangularPolygon &&
711
0
                    aIntegerTextureDeviceRect == aPolygonDeviceRect )
712
0
                {
713
0
                    rendering::RenderState aLocalState( renderState );
714
0
                    ::canvastools::appendToRenderState(aLocalState,
715
0
                                                         aTextureTransform);
716
0
                    ::basegfx::B2DHomMatrix aScaleCorrection;
717
0
                    aScaleCorrection.scale( 1.0/aBmpSize.Width,
718
0
                                            1.0/aBmpSize.Height );
719
0
                    ::canvastools::appendToRenderState(aLocalState,
720
0
                                                         aScaleCorrection);
721
722
                    // need alpha modulation?
723
0
                    if( !::rtl::math::approxEqual( textures[0].Alpha,
724
0
                                                   1.0 ) )
725
0
                    {
726
                        // setup alpha modulation values
727
0
                        aLocalState.DeviceColor.realloc(4);
728
0
                        double* pColor = aLocalState.DeviceColor.getArray();
729
0
                        pColor[0] =
730
0
                        pColor[1] =
731
0
                        pColor[2] = 0.0;
732
0
                        pColor[3] = textures[0].Alpha;
733
734
0
                        return drawBitmapModulated( pCanvas,
735
0
                                                    textures[0].Bitmap,
736
0
                                                    viewState,
737
0
                                                    aLocalState );
738
0
                    }
739
0
                    else
740
0
                    {
741
0
                        return drawBitmap( pCanvas,
742
0
                                           textures[0].Bitmap,
743
0
                                           viewState,
744
0
                                           aLocalState );
745
0
                    }
746
0
                }
747
0
                else
748
0
                {
749
                    // No easy mapping to drawBitmap() - calculate
750
                    // texturing parameters
751
                    // ===========================================
752
753
0
                    ::Bitmap aBmp( vclcanvastools::bitmapFromXBitmap( textures[0].Bitmap ) );
754
755
                    // scale down bitmap to [0,1]x[0,1] rect, as required
756
                    // from the XCanvas interface.
757
0
                    ::basegfx::B2DHomMatrix aScaling;
758
0
                    ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform
759
0
                    aScaling.scale( 1.0/aBmpSize.Width,
760
0
                                    1.0/aBmpSize.Height );
761
762
0
                    aTotalTransform = aTextureTransform * aScaling;
763
0
                    aPureTotalTransform = aTextureTransform;
764
765
                    // combine with view and render transform
766
0
                    ::basegfx::B2DHomMatrix aMatrix;
767
0
                    ::canvastools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);
768
769
                    // combine all three transformations into one
770
                    // global texture-to-device-space transformation
771
0
                    aTotalTransform *= aMatrix;
772
0
                    aPureTotalTransform *= aMatrix;
773
774
                    // analyze transformation, and setup an
775
                    // appropriate GraphicObject
776
0
                    ::basegfx::B2DVector aScale;
777
0
                    ::basegfx::B2DPoint  aOutputPos;
778
0
                    double               nRotate;
779
0
                    double               nShearX;
780
0
                    aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX );
781
782
0
                    GraphicAttr             aGrfAttr;
783
0
                    GraphicObjectSharedPtr  pGrfObj;
784
785
0
                    if( ::basegfx::fTools::equalZero( nShearX ) )
786
0
                    {
787
                        // no shear, GraphicObject is enough (the
788
                        // GraphicObject only supports scaling, rotation
789
                        // and translation)
790
791
                        // #i75339# don't apply mirror flags, having
792
                        // negative size values is enough to make
793
                        // GraphicObject flip the bitmap
794
795
                        // The angle has to be mapped from radian to tenths of
796
                        // degrees with the orientation reversed: [0,2Pi) ->
797
                        // (3600,0].  Note that the original angle may have
798
                        // values outside the [0,2Pi) interval.
799
0
                        const double nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg<10>(nRotate));
800
0
                        aGrfAttr.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees)) );
801
802
0
                        pGrfObj = std::make_shared<GraphicObject>( aBmp );
803
0
                    }
804
0
                    else
805
0
                    {
806
                        // modify output position, to account for the fact
807
                        // that transformBitmap() always normalizes its output
808
                        // bitmap into the smallest enclosing box.
809
0
                        ::basegfx::B2DRectangle aDestRect = ::canvastools::calcTransformedRectBounds(
810
0
                                                                    ::basegfx::B2DRectangle(0,
811
0
                                                                                            0,
812
0
                                                                                            aBmpSize.Width,
813
0
                                                                                            aBmpSize.Height),
814
0
                                                                    aMatrix );
815
816
0
                        aOutputPos.setX( aDestRect.getMinX() );
817
0
                        aOutputPos.setY( aDestRect.getMinY() );
818
819
                        // complex transformation, use generic affine bitmap
820
                        // transformation
821
0
                        aBmp = vclcanvastools::transformBitmap( aBmp, aTotalTransform);
822
823
0
                        pGrfObj = std::make_shared<GraphicObject>( aBmp );
824
825
                        // clear scale values, generated bitmap already
826
                        // contains scaling
827
0
                        aScale.setX( 1.0 ); aScale.setY( 1.0 );
828
829
                        // update bitmap size, bitmap has changed above.
830
0
                        aBmpSize = vcl::unotools::integerSize2DFromSize(aBmp.GetSizePixel());
831
0
                    }
832
833
834
                    // render texture tiled into polygon
835
                    // =================================
836
837
                    // calc device space direction vectors. We employ
838
                    // the following approach for tiled output: the
839
                    // texture bitmap is output in texture space
840
                    // x-major order, i.e. tile neighbors in texture
841
                    // space x direction are rendered back-to-back in
842
                    // device coordinate space (after the full device
843
                    // transformation). Thus, the aNextTile* vectors
844
                    // denote the output position updates in device
845
                    // space, to get from one tile to the next.
846
0
                    ::basegfx::B2DVector aNextTileX( 1.0, 0.0 );
847
0
                    ::basegfx::B2DVector aNextTileY( 0.0, 1.0 );
848
0
                    aNextTileX *= aPureTotalTransform;
849
0
                    aNextTileY *= aPureTotalTransform;
850
851
0
                    ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform );
852
853
0
                    ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(),
854
0
                                     "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
855
856
0
                    aInverseTextureTransform.invert();
857
858
                    // calc bound rect of extended texture area in
859
                    // device coordinates. Therefore, we first calc
860
                    // the area of the polygon bound rect in texture
861
                    // space. To maintain texture phase, this bound
862
                    // rect is then extended to integer coordinates
863
                    // (extended, because shrinking might leave some
864
                    // inner polygon areas unfilled).
865
                    // Finally, the bound rect is transformed back to
866
                    // device coordinate space, where we determine the
867
                    // start point from it.
868
0
                    ::basegfx::B2DRectangle aTextureSpacePolygonRect = ::canvastools::calcTransformedRectBounds(
869
0
                                                                vcl::unotools::b2DRectangleFromRectangle(aPolygonDeviceRect),
870
0
                                                                aInverseTextureTransform );
871
872
                    // calc left, top of extended polygon rect in
873
                    // texture space, create one-texture instance rect
874
                    // from it (i.e. rect from start point extending
875
                    // 1.0 units to the right and 1.0 units to the
876
                    // bottom). Note that the rounding employed here
877
                    // is a bit subtle, since we need to round up/down
878
                    // as _soon_ as any fractional amount is
879
                    // encountered. This is to ensure that the full
880
                    // polygon area is filled with texture tiles.
881
0
                    const sal_Int32 nX1( ::canvastools::roundDown( aTextureSpacePolygonRect.getMinX() ) );
882
0
                    const sal_Int32 nY1( ::canvastools::roundDown( aTextureSpacePolygonRect.getMinY() ) );
883
0
                    const sal_Int32 nX2( ::canvastools::roundUp( aTextureSpacePolygonRect.getMaxX() ) );
884
0
                    const sal_Int32 nY2( ::canvastools::roundUp( aTextureSpacePolygonRect.getMaxY() ) );
885
0
                    const ::basegfx::B2DRectangle aSingleTextureRect(
886
0
                        nX1, nY1,
887
0
                        nX1 + 1.0,
888
0
                        nY1 + 1.0 );
889
890
                    // and convert back to device space
891
0
                    ::basegfx::B2DRectangle aSingleDeviceTextureRect = ::canvastools::calcTransformedRectBounds(
892
0
                                                                aSingleTextureRect,
893
0
                                                                aPureTotalTransform );
894
895
0
                    const ::Point aPtRepeat( vcl::unotools::pointFromB2DPoint(
896
0
                                                 aSingleDeviceTextureRect.getMinimum() ) );
897
0
                    const ::Size  aSz( ::basegfx::fround<::tools::Long>( aScale.getX() * aBmpSize.Width ),
898
0
                                       ::basegfx::fround<::tools::Long>( aScale.getY() * aBmpSize.Height ) );
899
0
                    const ::Size  aIntegerNextTileX( vcl::unotools::sizeFromB2DSize(aNextTileX) );
900
0
                    const ::Size  aIntegerNextTileY( vcl::unotools::sizeFromB2DSize(aNextTileY) );
901
902
0
                    const ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
903
0
                                       ::basegfx::fround<::tools::Long>( aOutputPos.getX() ) : aPtRepeat.X(),
904
0
                                       textures[0].RepeatModeY == rendering::TexturingMode::NONE ?
905
0
                                       ::basegfx::fround<::tools::Long>( aOutputPos.getY() ) : aPtRepeat.Y() );
906
0
                    const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
907
0
                                             1 : nX2 - nX1 );
908
0
                    const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
909
0
                                             1 : nY2 - nY1 );
910
911
0
                    OutputDevice& rOutDev( mpOutDevProvider->getOutDev() );
912
913
0
                    if( bRectangularPolygon )
914
0
                    {
915
                        // use optimized output path
916
917
918
                        // this distinction really looks like a
919
                        // micro-optimization, but in fact greatly speeds up
920
                        // especially complex fills. That's because when using
921
                        // clipping, we can output polygons instead of
922
                        // poly-polygons, and don't have to output the gradient
923
                        // twice for XOR
924
925
                        // setup alpha modulation
926
0
                        if( !::rtl::math::approxEqual( textures[0].Alpha,
927
0
                                                       1.0 ) )
928
0
                        {
929
                            // TODO(F1): Note that the GraphicManager has
930
                            // a subtle difference in how it calculates
931
                            // the resulting alpha value: it's using the
932
                            // inverse alpha values (i.e. 'transparency'),
933
                            // and calculates transOrig + transModulate,
934
                            // instead of transOrig + transModulate -
935
                            // transOrig*transModulate (which would be
936
                            // equivalent to the origAlpha*modulateAlpha
937
                            // the DX canvas performs)
938
0
                            aGrfAttr.SetAlpha(
939
0
                                static_cast< sal_uInt8 >(
940
0
                                    ::basegfx::fround( 255.0 * textures[0].Alpha ) ) );
941
0
                        }
942
943
0
                        rOutDev.IntersectClipRegion( aPolygonDeviceRect );
944
0
                        textureFill( rOutDev,
945
0
                                     *pGrfObj,
946
0
                                     aPt,
947
0
                                     aIntegerNextTileX,
948
0
                                     aIntegerNextTileY,
949
0
                                     nTilesX,
950
0
                                     nTilesY,
951
0
                                     aSz,
952
0
                                     aGrfAttr );
953
954
0
                        if( mp2ndOutDevProvider )
955
0
                        {
956
0
                            OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
957
0
                            r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
958
0
                            textureFill( r2ndOutDev,
959
0
                                         *pGrfObj,
960
0
                                         aPt,
961
0
                                         aIntegerNextTileX,
962
0
                                         aIntegerNextTileY,
963
0
                                         nTilesX,
964
0
                                         nTilesY,
965
0
                                         aSz,
966
0
                                         aGrfAttr );
967
0
                        }
968
0
                    }
969
0
                    else
970
0
                    {
971
                        // output texture the hard way: XORing out the
972
                        // polygon
973
                        // ===========================================
974
975
0
                        if( !::rtl::math::approxEqual( textures[0].Alpha,
976
0
                                                       1.0 ) )
977
0
                        {
978
                            // uh-oh. alpha blending is required,
979
                            // cannot do direct XOR, but have to
980
                            // prepare the filled polygon within a
981
                            // VDev
982
0
                            ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
983
0
                            pVDev->SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
984
985
                            // shift output to origin of VDev
986
0
                            const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
987
0
                            aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
988
0
                                                          -aPolygonDeviceRect.Top() ) );
989
990
0
                            const vcl::Region aPolyClipRegion( aPolyPoly );
991
992
0
                            pVDev->SetClipRegion( aPolyClipRegion );
993
0
                            textureFill( *pVDev,
994
0
                                         *pGrfObj,
995
0
                                         aOutPos,
996
0
                                         aIntegerNextTileX,
997
0
                                         aIntegerNextTileY,
998
0
                                         nTilesX,
999
0
                                         nTilesY,
1000
0
                                         aSz,
1001
0
                                         aGrfAttr );
1002
1003
                            // output VDev content alpha-blended to
1004
                            // target position.
1005
0
                            const ::Point aEmptyPoint;
1006
0
                            Bitmap aContentBmp(
1007
0
                                pVDev->GetBitmap( aEmptyPoint,
1008
0
                                                 pVDev->GetOutputSizePixel() ) );
1009
1010
0
                            sal_uInt8 nCol( static_cast< sal_uInt8 >(
1011
0
                                           ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1012
0
                            AlphaMask aAlpha( pVDev->GetOutputSizePixel(),
1013
0
                                              &nCol );
1014
1015
0
                            Bitmap aOutputBmp( aContentBmp.CreateColorBitmap(), aAlpha );
1016
0
                            rOutDev.DrawBitmap( aPolygonDeviceRect.TopLeft(),
1017
0
                                                  aOutputBmp );
1018
1019
0
                            if( mp2ndOutDevProvider )
1020
0
                                mp2ndOutDevProvider->getOutDev().DrawBitmap( aPolygonDeviceRect.TopLeft(),
1021
0
                                                                       aOutputBmp );
1022
0
                        }
1023
0
                        else
1024
0
                        {
1025
0
                            const vcl::Region aPolyClipRegion( aPolyPoly );
1026
1027
0
                            rOutDev.Push( vcl::PushFlags::CLIPREGION );
1028
0
                            rOutDev.IntersectClipRegion( aPolyClipRegion );
1029
1030
0
                            textureFill( rOutDev,
1031
0
                                         *pGrfObj,
1032
0
                                         aPt,
1033
0
                                         aIntegerNextTileX,
1034
0
                                         aIntegerNextTileY,
1035
0
                                         nTilesX,
1036
0
                                         nTilesY,
1037
0
                                         aSz,
1038
0
                                         aGrfAttr );
1039
0
                            rOutDev.Pop();
1040
1041
0
                            if( mp2ndOutDevProvider )
1042
0
                            {
1043
0
                                OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
1044
0
                                auto popIt = r2ndOutDev.ScopedPush(vcl::PushFlags::CLIPREGION);
1045
1046
0
                                r2ndOutDev.IntersectClipRegion( aPolyClipRegion );
1047
0
                                textureFill( r2ndOutDev,
1048
0
                                             *pGrfObj,
1049
0
                                             aPt,
1050
0
                                             aIntegerNextTileX,
1051
0
                                             aIntegerNextTileY,
1052
0
                                             nTilesX,
1053
0
                                             nTilesY,
1054
0
                                             aSz,
1055
0
                                             aGrfAttr );
1056
0
                            }
1057
0
                        }
1058
0
                    }
1059
0
                }
1060
0
            }
1061
0
        }
1062
1063
        // TODO(P1): Provide caching here.
1064
0
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
1065
0
    }
1066
1067
}
1068
1069
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */