1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_canvas.hxx"
30 
31 #include <canvas/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 
34 #include <rtl/math.hxx>
35 
36 #include <com/sun/star/rendering/TextDirection.hpp>
37 #include <com/sun/star/rendering/TexturingMode.hpp>
38 #include <com/sun/star/rendering/PathCapType.hpp>
39 #include <com/sun/star/rendering/PathJoinType.hpp>
40 
41 #include <tools/poly.hxx>
42 #include <vcl/window.hxx>
43 #include <vcl/bitmapex.hxx>
44 #include <vcl/bmpacc.hxx>
45 #include <vcl/virdev.hxx>
46 #include <vcl/canvastools.hxx>
47 
48 #include <basegfx/matrix/b2dhommatrix.hxx>
49 #include <basegfx/range/b2drectangle.hxx>
50 #include <basegfx/point/b2dpoint.hxx>
51 #include <basegfx/vector/b2dsize.hxx>
52 #include <basegfx/polygon/b2dpolygon.hxx>
53 #include <basegfx/polygon/b2dpolygontools.hxx>
54 #include <basegfx/polygon/b2dpolypolygontools.hxx>
55 #include <basegfx/polygon/b2dlinegeometry.hxx>
56 #include <basegfx/tools/tools.hxx>
57 #include <basegfx/tools/lerp.hxx>
58 #include <basegfx/tools/keystoplerp.hxx>
59 #include <basegfx/tools/canvastools.hxx>
60 #include <basegfx/numeric/ftools.hxx>
61 
62 #include <comphelper/sequence.hxx>
63 
64 #include <canvas/canvastools.hxx>
65 #include <canvas/parametricpolypolygon.hxx>
66 
67 #include <boost/bind.hpp>
68 #include <boost/tuple/tuple.hpp>
69 
70 #include "spritecanvas.hxx"
71 #include "canvashelper.hxx"
72 #include "impltools.hxx"
73 
74 
75 using namespace ::com::sun::star;
76 
77 namespace vclcanvas
78 {
79     namespace
80     {
81         bool textureFill( OutputDevice&			rOutDev,
82                           GraphicObject&		rGraphic,
83                           const ::Point&		rPosPixel,
84                           const ::Size&			rNextTileX,
85                           const ::Size&			rNextTileY,
86                           sal_Int32				nTilesX,
87                           sal_Int32				nTilesY,
88                           const ::Size&			rTileSize,
89                           const GraphicAttr&	rAttr)
90         {
91             bool bRet( false );
92             Point 	aCurrPos;
93             int 	nX, nY;
94 
95             for( nY=0; nY < nTilesY; ++nY )
96             {
97                 aCurrPos.X() = rPosPixel.X() + nY*rNextTileY.Width();
98                 aCurrPos.Y() = rPosPixel.Y() + nY*rNextTileY.Height();
99 
100                 for( nX=0; nX < nTilesX; ++nX )
101                 {
102                     // update return value. This method should return true, if
103                     // at least one of the looped Draws succeeded.
104                     bRet |= ( sal_True == rGraphic.Draw( &rOutDev,
105                                            aCurrPos,
106                                            rTileSize,
107                                            &rAttr ) );
108 
109                     aCurrPos.X() += rNextTileX.Width();
110                     aCurrPos.Y() += rNextTileX.Height();
111                 }
112             }
113 
114             return bRet;
115         }
116 
117 
118         /** Fill linear or axial gradient
119 
120         	Since most of the code for linear and axial gradients are
121         	the same, we've a unified method here
122          */
123         void fillLinearGradient( OutputDevice&					                rOutDev,
124                                  const ::basegfx::B2DHomMatrix&	                rTextureTransform,
125                                  const ::Rectangle&				                rBounds,
126                                  unsigned int								    nStepCount,
127                                  const ::canvas::ParametricPolyPolygon::Values& rValues,
128                                  const std::vector< ::Color >&                  rColors )
129         {
130             // determine general position of gradient in relation to
131             // the bound rect
132             // =====================================================
133 
134             ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
135             ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
136             ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
137             ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
138 
139             aLeftTop	*= rTextureTransform;
140             aLeftBottom *= rTextureTransform;
141             aRightTop 	*= rTextureTransform;
142             aRightBottom*= rTextureTransform;
143 
144             // calc length of bound rect diagonal
145             const ::basegfx::B2DVector aBoundRectDiagonal(
146                 ::vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) -
147                 ::vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) );
148             const double nDiagonalLength( aBoundRectDiagonal.getLength() );
149 
150             // create direction of gradient:
151             //     _______
152             //     |  |  |
153             // ->  |  |  | ...
154             //     |  |  |
155             //     -------
156             ::basegfx::B2DVector aDirection( aRightTop - aLeftTop );
157             aDirection.normalize();
158 
159             // now, we potentially have to enlarge our gradient area
160             // atop and below the transformed [0,1]x[0,1] unit rect,
161             // for the gradient to fill the complete bound rect.
162             ::basegfx::tools::infiniteLineFromParallelogram( aLeftTop,
163                                                              aLeftBottom,
164                                                              aRightTop,
165                                                              aRightBottom,
166                                                              ::vcl::unotools::b2DRectangleFromRectangle( rBounds ) );
167 
168 
169             // render gradient
170             // ===============
171 
172             // for linear gradients, it's easy to render
173             // non-overlapping polygons: just split the gradient into
174             // nStepCount small strips. Prepare the strip now.
175 
176             // For performance reasons, we create a temporary VCL
177             // polygon here, keep it all the way and only change the
178             // vertex values in the loop below (as ::Polygon is a
179             // pimpl class, creating one every loop turn would really
180             // stress the mem allocator)
181             ::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
182 
183             OSL_ENSURE( nStepCount >= 3,
184                         "fillLinearGradient(): stepcount smaller than 3" );
185 
186 
187             // fill initial strip (extending two times the bound rect's
188             // diagonal to the 'left'
189             // ------------------------------------------------------
190 
191             // calculate left edge, by moving left edge of the
192             // gradient rect two times the bound rect's diagonal to
193             // the 'left'. Since we postpone actual rendering into the
194             // loop below, we set the _right_ edge here, which will be
195             // readily copied into the left edge in the loop below
196             const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
197             aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ),
198                                     ::basegfx::fround( rPoint1.getY() ) );
199 
200             const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection );
201             aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ),
202                                     ::basegfx::fround( rPoint2.getY() ) );
203 
204 
205             // iteratively render all other strips
206             // -----------------------------------
207 
208             // ensure that nStepCount matches color stop parity, to
209             // have a well-defined middle color e.g. for axial
210             // gradients.
211             if( (rColors.size() % 2) != (nStepCount % 2) )
212                 ++nStepCount;
213 
214             rOutDev.SetLineColor();
215 
216             basegfx::tools::KeyStopLerp aLerper(rValues.maStops);
217 
218             // only iterate nStepCount-1 steps, as the last strip is
219             // explicitely painted below
220             for( unsigned int i=0; i<nStepCount-1; ++i )
221             {
222                 std::ptrdiff_t nIndex;
223                 double fAlpha;
224                 boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount);
225 
226                 rOutDev.SetFillColor(
227                     Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
228                            (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
229                            (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
230 
231                 // copy right egde of polygon to left edge (and also
232                 // copy the closing point)
233                 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
234                 aTempPoly[3] = aTempPoly[2];
235 
236                 // calculate new right edge, from interpolating
237                 // between start and end line. Note that i is
238                 // increased by one, to account for the fact that we
239                 // calculate the right border here (whereas the fill
240                 // color is governed by the left edge)
241                 const ::basegfx::B2DPoint& rPoint3(
242                     (nStepCount - i-1)/double(nStepCount)*aLeftTop +
243                     (i+1)/double(nStepCount)*aRightTop );
244                 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ),
245                                         ::basegfx::fround( rPoint3.getY() ) );
246 
247                 const ::basegfx::B2DPoint& rPoint4(
248                     (nStepCount - i-1)/double(nStepCount)*aLeftBottom +
249                     (i+1)/double(nStepCount)*aRightBottom );
250                 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ),
251                                         ::basegfx::fround( rPoint4.getY() ) );
252 
253                 rOutDev.DrawPolygon( aTempPoly );
254             }
255 
256             // fill final strip (extending two times the bound rect's
257             // diagonal to the 'right'
258             // ------------------------------------------------------
259 
260             // copy right egde of polygon to left edge (and also
261             // copy the closing point)
262             aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
263             aTempPoly[3] = aTempPoly[2];
264 
265             // calculate new right edge, by moving right edge of the
266             // gradient rect two times the bound rect's diagonal to
267             // the 'right'.
268             const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
269             aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ),
270                                                    ::basegfx::fround( rPoint3.getY() ) );
271 
272             const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection );
273             aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ),
274                                     ::basegfx::fround( rPoint4.getY() ) );
275 
276             rOutDev.SetFillColor( rColors.back() );
277 
278             rOutDev.DrawPolygon( aTempPoly );
279         }
280 
281         void fillPolygonalGradient( OutputDevice&                                  rOutDev,
282                                     const ::basegfx::B2DHomMatrix&                 rTextureTransform,
283                                     const ::Rectangle&                             rBounds,
284                                     unsigned int                                   nStepCount,
285                                     bool                                           bFillNonOverlapping,
286                                     const ::canvas::ParametricPolyPolygon::Values& rValues,
287                                     const std::vector< ::Color >&                  rColors )
288         {
289             const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
290 
291             ENSURE_OR_THROW( rGradientPoly.count() > 2,
292                               "fillPolygonalGradient(): polygon without area given" );
293 
294             // For performance reasons, we create a temporary VCL polygon
295             // here, keep it all the way and only change the vertex values
296             // in the loop below (as ::Polygon is a pimpl class, creating
297             // one every loop turn would really stress the mem allocator)
298             ::basegfx::B2DPolygon 	aOuterPoly( rGradientPoly );
299             ::basegfx::B2DPolygon 	aInnerPoly;
300 
301             // subdivide polygon _before_ rendering, would otherwise have
302             // to be performed on every loop turn.
303             if( aOuterPoly.areControlPointsUsed() )
304                 aOuterPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aOuterPoly);
305 
306             aInnerPoly = aOuterPoly;
307 
308             // only transform outer polygon _after_ copying it into
309             // aInnerPoly, because inner polygon has to be scaled before
310             // the actual texture transformation takes place
311             aOuterPoly.transform( rTextureTransform );
312 
313             // determine overall transformation for inner polygon (might
314             // have to be prefixed by anisotrophic scaling)
315             ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
316 
317 
318             // apply scaling (possibly anisotrophic) to inner polygon
319             // ------------------------------------------------------
320 
321             // scale inner polygon according to aspect ratio: for
322             // wider-than-tall bounds (nAspectRatio > 1.0), the inner
323             // polygon, representing the gradient focus, must have
324             // non-zero width. Specifically, a bound rect twice as wide as
325             // tall has a focus polygon of half it's width.
326             const double nAspectRatio( rValues.mnAspectRatio );
327             if( nAspectRatio > 1.0 )
328             {
329                 // width > height case
330                 aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
331                                                     0.0 );
332             }
333             else if( nAspectRatio < 1.0 )
334             {
335                 // width < height case
336                 aInnerPolygonTransformMatrix.scale( 0.0,
337                                                     1.0 - nAspectRatio );
338             }
339             else
340             {
341                 // isotrophic case
342                 aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
343             }
344 
345             // and finally, add texture transform to it.
346             aInnerPolygonTransformMatrix *= rTextureTransform;
347 
348             // apply final matrix to polygon
349             aInnerPoly.transform( aInnerPolygonTransformMatrix );
350 
351 
352             const sal_uInt32 nNumPoints( aOuterPoly.count() );
353             ::Polygon		 aTempPoly( static_cast<sal_uInt16>(nNumPoints+1) );
354 
355             // increase number of steps by one: polygonal gradients have
356             // the outermost polygon rendered in rColor2, and the
357             // innermost in rColor1. The innermost polygon will never
358             // have zero area, thus, we must divide the interval into
359             // nStepCount+1 steps. For example, to create 3 steps:
360             //
361             // |                       |
362             // |-------|-------|-------|
363             // |                       |
364             // 3       2       1       0
365             //
366             // This yields 4 tick marks, where 0 is never attained (since
367             // zero-area polygons typically don't display perceivable
368             // color).
369             ++nStepCount;
370 
371             rOutDev.SetLineColor();
372 
373             basegfx::tools::KeyStopLerp aLerper(rValues.maStops);
374 
375             if( !bFillNonOverlapping )
376             {
377                 // fill background
378                 rOutDev.SetFillColor( rColors.front() );
379                 rOutDev.DrawRect( rBounds );
380 
381                 // render polygon
382                 // ==============
383 
384                 for( unsigned int i=1,p; i<nStepCount; ++i )
385                 {
386                     const double fT( i/double(nStepCount) );
387 
388                     std::ptrdiff_t nIndex;
389                     double fAlpha;
390                     boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT);
391 
392                     // lerp color
393                     rOutDev.SetFillColor(
394                         Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
395                                (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
396                                (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
397 
398                     // scale and render polygon, by interpolating between
399                     // outer and inner polygon.
400 
401                     for( p=0; p<nNumPoints; ++p )
402                     {
403                         const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
404                         const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
405 
406                         aTempPoly[(sal_uInt16)p] = ::Point(
407                             basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
408                             basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
409                     }
410 
411                     // close polygon explicitely
412                     aTempPoly[(sal_uInt16)p] = aTempPoly[0];
413 
414                     // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
415                     // OutputDevice::ImplDrawComplexGradient(), there's a note
416                     // that on some VDev's, rendering disjunct poly-polygons
417                     // is faster!
418                     rOutDev.DrawPolygon( aTempPoly );
419                 }
420             }
421             else
422             {
423                 // render polygon
424                 // ==============
425 
426                 // For performance reasons, we create a temporary VCL polygon
427                 // here, keep it all the way and only change the vertex values
428                 // in the loop below (as ::Polygon is a pimpl class, creating
429                 // one every loop turn would really stress the mem allocator)
430                 ::PolyPolygon			aTempPolyPoly;
431                 ::Polygon				aTempPoly2( static_cast<sal_uInt16>(nNumPoints+1) );
432 
433                 aTempPoly2[0] = rBounds.TopLeft();
434                 aTempPoly2[1] = rBounds.TopRight();
435                 aTempPoly2[2] = rBounds.BottomRight();
436                 aTempPoly2[3] = rBounds.BottomLeft();
437                 aTempPoly2[4] = rBounds.TopLeft();
438 
439                 aTempPolyPoly.Insert( aTempPoly );
440                 aTempPolyPoly.Insert( aTempPoly2 );
441 
442                 for( unsigned int i=0,p; i<nStepCount; ++i )
443                 {
444                     const double fT( (i+1)/double(nStepCount) );
445 
446                     std::ptrdiff_t nIndex;
447                     double fAlpha;
448                     boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT);
449 
450                     // lerp color
451                     rOutDev.SetFillColor(
452                         Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
453                                (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
454                                (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
455 
456 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
457                     if( i && !(i % 10) )
458                         rOutDev.SetFillColor( COL_RED );
459 #endif
460 
461                     // scale and render polygon. Note that here, we
462                     // calculate the inner polygon, which is actually the
463                     // start of the _next_ color strip. Thus, i+1
464 
465                     for( p=0; p<nNumPoints; ++p )
466                     {
467                         const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
468                         const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
469 
470                         aTempPoly[(sal_uInt16)p] = ::Point(
471                             basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
472                             basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
473                     }
474 
475                     // close polygon explicitely
476                     aTempPoly[(sal_uInt16)p] = aTempPoly[0];
477 
478                     // swap inner and outer polygon
479                     aTempPolyPoly.Replace( aTempPolyPoly.GetObject( 1 ), 0 );
480 
481                     if( i+1<nStepCount )
482                     {
483                         // assign new inner polygon. Note that with this
484                         // formulation, the internal pimpl objects for both
485                         // temp polygons and the polypolygon remain identical,
486                         // minimizing heap accesses (only a Polygon wrapper
487                         // object is freed and deleted twice during this swap).
488                         aTempPolyPoly.Replace( aTempPoly, 1 );
489                     }
490                     else
491                     {
492                         // last, i.e. inner strip. Now, the inner polygon
493                         // has zero area anyway, and to not leave holes in
494                         // the gradient, finally render a simple polygon:
495                         aTempPolyPoly.Remove( 1 );
496                     }
497 
498                     rOutDev.DrawPolyPolygon( aTempPolyPoly );
499                 }
500             }
501         }
502 
503         void doGradientFill( OutputDevice&                                  rOutDev,
504                              const ::canvas::ParametricPolyPolygon::Values&	rValues,
505                              const std::vector< ::Color >&                  rColors,
506                              const ::basegfx::B2DHomMatrix&                 rTextureTransform,
507                              const ::Rectangle&                             rBounds,
508                              unsigned int                                   nStepCount,
509                              bool                                           bFillNonOverlapping )
510         {
511             switch( rValues.meType )
512             {
513                 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR:
514                     fillLinearGradient( rOutDev,
515                                         rTextureTransform,
516                                         rBounds,
517                                         nStepCount,
518                                         rValues,
519                                         rColors );
520                     break;
521 
522                 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL:
523                     // FALLTHROUGH intended
524                 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR:
525                     fillPolygonalGradient( rOutDev,
526                                            rTextureTransform,
527                                            rBounds,
528                                            nStepCount,
529                                            bFillNonOverlapping,
530                                            rValues,
531                                            rColors );
532                     break;
533 
534                 default:
535                     ENSURE_OR_THROW( false,
536                                       "CanvasHelper::doGradientFill(): Unexpected case" );
537             }
538         }
539 
540         int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 )
541         {
542             return ::std::max(
543                 labs( rColor1.GetRed() - rColor2.GetRed() ),
544                 ::std::max(
545                     labs( rColor1.GetGreen() - rColor2.GetGreen() ),
546                     labs( rColor1.GetBlue()  - rColor2.GetBlue() ) ) );
547         }
548 
549         bool gradientFill( OutputDevice&                                   rOutDev,
550                            OutputDevice*                                   p2ndOutDev,
551                            const ::canvas::ParametricPolyPolygon::Values&  rValues,
552                            const std::vector< ::Color >&                   rColors,
553                            const PolyPolygon&                              rPoly,
554                            const rendering::ViewState&                     viewState,
555                            const rendering::RenderState&                   renderState,
556                            const rendering::Texture&                       texture,
557                            int                                             nTransparency )
558         {
559             (void)nTransparency;
560 
561             // TODO(T2): It is maybe necessary to lock here, should
562             // maGradientPoly someday cease to be const. But then, beware of
563             // deadlocks, canvashelper calls this method with locked own
564             // mutex.
565 
566             // calc step size
567             // --------------
568             int nColorSteps = 0;
569             for( size_t i=0; i<rColors.size()-1; ++i )
570                 nColorSteps += numColorSteps(rColors[i],rColors[i+1]);
571 
572             ::basegfx::B2DHomMatrix aTotalTransform;
573             const int nStepCount=
574                 ::canvas::tools::calcGradientStepCount(aTotalTransform,
575                                                        viewState,
576                                                        renderState,
577                                                        texture,
578                                                        nColorSteps);
579 
580             rOutDev.SetLineColor();
581 
582             // determine maximal bound rect of texture-filled
583             // polygon
584             const ::Rectangle aPolygonDeviceRectOrig(
585                 rPoly.GetBoundRect() );
586 
587             if( tools::isRectangle( rPoly ) )
588             {
589                 // use optimized output path
590                 // -------------------------
591 
592                 // this distinction really looks like a
593                 // micro-optimisation, but in fact greatly speeds up
594                 // especially complex gradients. That's because when using
595                 // clipping, we can output polygons instead of
596                 // poly-polygons, and don't have to output the gradient
597                 // twice for XOR
598 
599                 rOutDev.Push( PUSH_CLIPREGION );
600                 rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
601                 doGradientFill( rOutDev,
602                                 rValues,
603                                 rColors,
604                                 aTotalTransform,
605                                 aPolygonDeviceRectOrig,
606                                 nStepCount,
607                                 false );
608                 rOutDev.Pop();
609 
610                 if( p2ndOutDev )
611                 {
612                     p2ndOutDev->Push( PUSH_CLIPREGION );
613                     p2ndOutDev->IntersectClipRegion( aPolygonDeviceRectOrig );
614                     doGradientFill( *p2ndOutDev,
615                                     rValues,
616                                     rColors,
617                                     aTotalTransform,
618                                     aPolygonDeviceRectOrig,
619                                     nStepCount,
620                                     false );
621                     p2ndOutDev->Pop();
622                 }
623             }
624             else
625 #if defined(QUARTZ) // TODO: other ports should avoid the XOR-trick too (implementation vs. interface!)
626             {
627                 const Region aPolyClipRegion( rPoly );
628 
629                 rOutDev.Push( PUSH_CLIPREGION );
630                 rOutDev.SetClipRegion( aPolyClipRegion );
631 
632                 doGradientFill( rOutDev,
633                                 rValues,
634                                 rColors,
635                                 aTotalTransform,
636                                 aPolygonDeviceRectOrig,
637                                 nStepCount,
638                                 false );
639                 rOutDev.Pop();
640 
641                 if( p2ndOutDev )
642                 {
643                     p2ndOutDev->Push( PUSH_CLIPREGION );
644                     p2ndOutDev->SetClipRegion( aPolyClipRegion );
645                     doGradientFill( *p2ndOutDev,
646                                     rValues,
647                                     rColors,
648                                     aTotalTransform,
649                                     aPolygonDeviceRectOrig,
650                                     nStepCount,
651                                     false );
652                     p2ndOutDev->Pop();
653                 }
654             }
655 #else // TODO: remove once doing the XOR-trick in the canvas-layer becomes redundant
656             {
657                 // output gradient the hard way: XORing out the polygon
658                 rOutDev.Push( PUSH_RASTEROP );
659                 rOutDev.SetRasterOp( ROP_XOR );
660                 doGradientFill( rOutDev,
661                                 rValues,
662                                 rColors,
663                                 aTotalTransform,
664                                 aPolygonDeviceRectOrig,
665                                 nStepCount,
666                                 true );
667                 rOutDev.SetFillColor( COL_BLACK );
668                 rOutDev.SetRasterOp( ROP_0 );
669                 rOutDev.DrawPolyPolygon( rPoly );
670                 rOutDev.SetRasterOp( ROP_XOR );
671                 doGradientFill( rOutDev,
672                                 rValues,
673                                 rColors,
674                                 aTotalTransform,
675                                 aPolygonDeviceRectOrig,
676                                 nStepCount,
677                                 true );
678                 rOutDev.Pop();
679 
680                 if( p2ndOutDev )
681                 {
682                     p2ndOutDev->Push( PUSH_RASTEROP );
683                     p2ndOutDev->SetRasterOp( ROP_XOR );
684                     doGradientFill( *p2ndOutDev,
685                                     rValues,
686                                     rColors,
687                                     aTotalTransform,
688                                     aPolygonDeviceRectOrig,
689                                     nStepCount,
690                                     true );
691                     p2ndOutDev->SetFillColor( COL_BLACK );
692                     p2ndOutDev->SetRasterOp( ROP_0 );
693                     p2ndOutDev->DrawPolyPolygon( rPoly );
694                     p2ndOutDev->SetRasterOp( ROP_XOR );
695                     doGradientFill( *p2ndOutDev,
696                                     rValues,
697                                     rColors,
698                                     aTotalTransform,
699                                     aPolygonDeviceRectOrig,
700                                     nStepCount,
701                                     true );
702                     p2ndOutDev->Pop();
703                 }
704             }
705 #endif // complex-clipping vs. XOR-trick
706 
707 #if 0 //defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
708             {
709                 ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
710                 ::basegfx::B2DRectangle aTextureDeviceRect;
711                 ::basegfx::B2DHomMatrix aTextureTransform;
712                 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
713                                                             aRect,
714                                                             aTextureTransform );
715                 rOutDev.SetLineColor( COL_RED );
716                 rOutDev.SetFillColor();
717                 rOutDev.DrawRect( ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
718 
719                 rOutDev.SetLineColor( COL_BLUE );
720                 ::Polygon aPoly1(
721                     ::vcl::unotools::rectangleFromB2DRectangle( aRect ));
722                 ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() );
723                 aPoly2.transform( aTextureTransform );
724                 ::Polygon aPoly3( aPoly2 );
725                 rOutDev.DrawPolygon( aPoly3 );
726             }
727 #endif
728 
729             return true;
730         }
731     }
732 
733     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* 							pCanvas,
734                                                                                          const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
735                                                                                          const rendering::ViewState& 						viewState,
736                                                                                          const rendering::RenderState& 						renderState,
737                                                                                          const uno::Sequence< rendering::Texture >& 		textures )
738     {
739         ENSURE_ARG_OR_THROW( xPolyPolygon.is(),
740                          "CanvasHelper::fillPolyPolygon(): polygon is NULL");
741         ENSURE_ARG_OR_THROW( textures.getLength(),
742                          "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
743 
744         if( mpOutDev )
745         {
746             tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
747 
748             const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) );
749             PolyPolygon aPolyPoly( tools::mapPolyPolygon(
750                                        ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon),
751                                        viewState, renderState ) );
752 
753             // TODO(F1): Multi-texturing
754             if( textures[0].Gradient.is() )
755             {
756                 // try to cast XParametricPolyPolygon2D reference to
757                 // our implementation class.
758                 ::canvas::ParametricPolyPolygon* pGradient =
759                       dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
760 
761                 if( pGradient && pGradient->getValues().maColors.getLength() )
762                 {
763                     // copy state from Gradient polypoly locally
764                     // (given object might change!)
765                     const ::canvas::ParametricPolyPolygon::Values& rValues(
766                         pGradient->getValues() );
767 
768                     if( rValues.maColors.getLength() < 2 )
769                     {
770                         rendering::RenderState aTempState=renderState;
771                         aTempState.DeviceColor = rValues.maColors[0];
772                         fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState);
773                     }
774                     else
775                     {
776                         std::vector< ::Color > aColors(rValues.maColors.getLength());
777                         std::transform(&rValues.maColors[0],
778                                        &rValues.maColors[0]+rValues.maColors.getLength(),
779                                        aColors.begin(),
780                                        boost::bind(
781                                            &vcl::unotools::stdColorSpaceSequenceToColor,
782                                            _1));
783 
784                         // TODO(E1): Return value
785                         // TODO(F1): FillRule
786                         gradientFill( mpOutDev->getOutDev(),
787                                       mp2ndOutDev.get() ? &mp2ndOutDev->getOutDev() : (OutputDevice*)NULL,
788                                       rValues,
789                                       aColors,
790                                       aPolyPoly,
791                                       viewState,
792                                       renderState,
793                                       textures[0],
794                                       nTransparency );
795                     }
796                 }
797                 else
798                 {
799                     // TODO(F1): The generic case is missing here
800                     ENSURE_OR_THROW( false,
801                                       "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
802                 }
803             }
804             else if( textures[0].Bitmap.is() )
805             {
806                 const geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() );
807 
808                 ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
809                                  aBmpSize.Height != 0,
810                                  "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
811 
812                 // determine maximal bound rect of texture-filled
813                 // polygon
814                 const ::Rectangle aPolygonDeviceRect(
815                     aPolyPoly.GetBoundRect() );
816 
817 
818                 // first of all, determine whether we have a
819                 // drawBitmap() in disguise
820                 // =========================================
821 
822                 const bool bRectangularPolygon( tools::isRectangle( aPolyPoly ) );
823 
824                 ::basegfx::B2DHomMatrix aTotalTransform;
825                 ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform,
826                                                              viewState,
827                                                              renderState);
828                 ::basegfx::B2DHomMatrix aTextureTransform;
829                 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
830                                                                 textures[0].AffineTransform );
831 
832                 aTotalTransform *= aTextureTransform;
833 
834                 const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
835                 ::basegfx::B2DRectangle aTextureDeviceRect;
836                 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
837                                                             aRect,
838                                                             aTotalTransform );
839 
840                 const ::Rectangle aIntegerTextureDeviceRect(
841                     ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
842 
843                 if( bRectangularPolygon &&
844                     aIntegerTextureDeviceRect == aPolygonDeviceRect )
845                 {
846                     rendering::RenderState aLocalState( renderState );
847                     ::canvas::tools::appendToRenderState(aLocalState,
848                                                          aTextureTransform);
849                     ::basegfx::B2DHomMatrix aScaleCorrection;
850                     aScaleCorrection.scale( 1.0/aBmpSize.Width,
851                                             1.0/aBmpSize.Height );
852                     ::canvas::tools::appendToRenderState(aLocalState,
853                                                          aScaleCorrection);
854 
855                     // need alpha modulation?
856                     if( !::rtl::math::approxEqual( textures[0].Alpha,
857                                                    1.0 ) )
858                     {
859                         // setup alpha modulation values
860                         aLocalState.DeviceColor.realloc(4);
861                         double* pColor = aLocalState.DeviceColor.getArray();
862                         pColor[0] =
863                         pColor[1] =
864                         pColor[2] = 0.0;
865                         pColor[3] = textures[0].Alpha;
866 
867                         return drawBitmapModulated( pCanvas,
868                                                     textures[0].Bitmap,
869                                                     viewState,
870                                                     aLocalState );
871                     }
872                     else
873                     {
874                         return drawBitmap( pCanvas,
875                                            textures[0].Bitmap,
876                                            viewState,
877                                            aLocalState );
878                     }
879                 }
880                 else
881                 {
882                     // No easy mapping to drawBitmap() - calculate
883                     // texturing parameters
884                     // ===========================================
885 
886                     BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) );
887 
888                     // scale down bitmap to [0,1]x[0,1] rect, as required
889                     // from the XCanvas interface.
890                     ::basegfx::B2DHomMatrix aScaling;
891                     ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform
892                     aScaling.scale( 1.0/aBmpSize.Width,
893                                     1.0/aBmpSize.Height );
894 
895                     aTotalTransform = aTextureTransform * aScaling;
896                     aPureTotalTransform = aTextureTransform;
897 
898                     // combine with view and render transform
899                     ::basegfx::B2DHomMatrix aMatrix;
900                     ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);
901 
902                     // combine all three transformations into one
903                     // global texture-to-device-space transformation
904                     aTotalTransform *= aMatrix;
905                     aPureTotalTransform *= aMatrix;
906 
907                     // analyze transformation, and setup an
908                     // appropriate GraphicObject
909                     ::basegfx::B2DVector aScale;
910                     ::basegfx::B2DPoint  aOutputPos;
911                     double				 nRotate;
912                     double				 nShearX;
913                     aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX );
914 
915                     GraphicAttr 			aGrfAttr;
916                     GraphicObjectSharedPtr 	pGrfObj;
917 
918                     if( ::basegfx::fTools::equalZero( nShearX ) )
919                     {
920                         // no shear, GraphicObject is enough (the
921                         // GraphicObject only supports scaling, rotation
922                         // and translation)
923 
924                         // setup GraphicAttr
925                         aGrfAttr.SetMirrorFlags(
926                             ( aScale.getX() < 0.0 ? BMP_MIRROR_HORZ : 0 ) |
927                             ( aScale.getY() < 0.0 ? BMP_MIRROR_VERT : 0 ) );
928                         aGrfAttr.SetRotation( static_cast< sal_uInt16 >(::basegfx::fround( nRotate*10.0 )) );
929 
930                         pGrfObj.reset( new GraphicObject( aBmpEx ) );
931                     }
932                     else
933                     {
934                         // complex transformation, use generic affine bitmap
935                         // transformation
936                         aBmpEx = tools::transformBitmap( aBmpEx,
937                                                          aTotalTransform,
938                                                          uno::Sequence< double >(),
939                                                          tools::MODULATE_NONE);
940 
941                         pGrfObj.reset( new GraphicObject( aBmpEx ) );
942 
943                         // clear scale values, generated bitmap already
944                         // contains scaling
945                         aScale.setX( 0.0 ); aScale.setY( 0.0 );
946                     }
947 
948 
949                     // render texture tiled into polygon
950                     // =================================
951 
952                     // calc device space direction vectors. We employ
953                     // the followin approach for tiled output: the
954                     // texture bitmap is output in texture space
955                     // x-major order, i.e. tile neighbors in texture
956                     // space x direction are rendered back-to-back in
957                     // device coordinate space (after the full device
958                     // transformation). Thus, the aNextTile* vectors
959                     // denote the output position updates in device
960                     // space, to get from one tile to the next.
961                     ::basegfx::B2DVector aNextTileX( 1.0, 0.0 );
962                     ::basegfx::B2DVector aNextTileY( 0.0, 1.0 );
963                     aNextTileX *= aPureTotalTransform;
964                     aNextTileY *= aPureTotalTransform;
965 
966                     ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform );
967 
968                     ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(),
969                                      "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
970 
971                     aInverseTextureTransform.invert();
972 
973                     // calc bound rect of extended texture area in
974                     // device coordinates. Therefore, we first calc
975                     // the area of the polygon bound rect in texture
976                     // space. To maintain texture phase, this bound
977                     // rect is then extended to integer coordinates
978                     // (extended, because shrinking might leave some
979                     // inner polygon areas unfilled).
980                     // Finally, the bound rect is transformed back to
981                     // device coordinate space, were we determine the
982                     // start point from it.
983                     ::basegfx::B2DRectangle aTextureSpacePolygonRect;
984                     ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect,
985                                                                 ::vcl::unotools::b2DRectangleFromRectangle(
986                                                                     aPolygonDeviceRect ),
987                                                                 aInverseTextureTransform );
988 
989                     // calc left, top of extended polygon rect in
990                     // texture space, create one-texture instance rect
991                     // from it (i.e. rect from start point extending
992                     // 1.0 units to the right and 1.0 units to the
993                     // bottom). Note that the rounding employed here
994                     // is a bit subtle, since we need to round up/down
995                     // as _soon_ as any fractional amount is
996                     // encountered. This is to ensure that the full
997                     // polygon area is filled with texture tiles.
998                     const sal_Int32 nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) );
999                     const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) );
1000                     const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) );
1001                     const sal_Int32 nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxY() ) );
1002                     const ::basegfx::B2DRectangle aSingleTextureRect(
1003                         nX1, nY1,
1004                         nX1 + 1.0,
1005                         nY1 + 1.0 );
1006 
1007                     // and convert back to device space
1008                     ::basegfx::B2DRectangle aSingleDeviceTextureRect;
1009                     ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect,
1010                                                                 aSingleTextureRect,
1011                                                                 aPureTotalTransform );
1012 
1013                     const ::Point aPtRepeat( ::vcl::unotools::pointFromB2DPoint(
1014                                                  aSingleDeviceTextureRect.getMinimum() ) );
1015                     const ::Size  aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ),
1016                                        ::basegfx::fround( aScale.getY() * aBmpSize.Height ) );
1017                     const ::Size  aIntegerNextTileX( ::vcl::unotools::sizeFromB2DSize(aNextTileX) );
1018                     const ::Size  aIntegerNextTileY( ::vcl::unotools::sizeFromB2DSize(aNextTileY) );
1019 
1020                     const ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
1021                                        ::basegfx::fround( aOutputPos.getX() ) : aPtRepeat.X(),
1022                                        textures[0].RepeatModeY == rendering::TexturingMode::NONE ?
1023                                        ::basegfx::fround( aOutputPos.getY() ) : aPtRepeat.Y() );
1024                     const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
1025                                              1 : nX2 - nX1 );
1026                     const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
1027                                              1 : nY2 - nY1 );
1028 
1029                     OutputDevice& rOutDev( mpOutDev->getOutDev() );
1030 
1031                     if( bRectangularPolygon )
1032                     {
1033                         // use optimized output path
1034                         // -------------------------
1035 
1036                         // this distinction really looks like a
1037                         // micro-optimisation, but in fact greatly speeds up
1038                         // especially complex fills. That's because when using
1039                         // clipping, we can output polygons instead of
1040                         // poly-polygons, and don't have to output the gradient
1041                         // twice for XOR
1042 
1043                         // setup alpha modulation
1044                         if( !::rtl::math::approxEqual( textures[0].Alpha,
1045                                                        1.0 ) )
1046                         {
1047                             // TODO(F1): Note that the GraphicManager has
1048                             // a subtle difference in how it calculates
1049                             // the resulting alpha value: it's using the
1050                             // inverse alpha values (i.e. 'transparency'),
1051                             // and calculates transOrig + transModulate,
1052                             // instead of transOrig + transModulate -
1053                             // transOrig*transModulate (which would be
1054                             // equivalent to the origAlpha*modulateAlpha
1055                             // the DX canvas performs)
1056                             aGrfAttr.SetTransparency(
1057                                 static_cast< sal_uInt8 >(
1058                                     ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1059                         }
1060 
1061                         rOutDev.IntersectClipRegion( aPolygonDeviceRect );
1062                         textureFill( rOutDev,
1063                                      *pGrfObj,
1064                                      aPt,
1065                                      aIntegerNextTileX,
1066                                      aIntegerNextTileY,
1067                                      nTilesX,
1068                                      nTilesY,
1069                                      aSz,
1070                                      aGrfAttr );
1071 
1072                         if( mp2ndOutDev )
1073                         {
1074                             OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1075                             r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
1076                             textureFill( r2ndOutDev,
1077                                          *pGrfObj,
1078                                          aPt,
1079                                          aIntegerNextTileX,
1080                                          aIntegerNextTileY,
1081                                          nTilesX,
1082                                          nTilesY,
1083                                          aSz,
1084                                          aGrfAttr );
1085                         }
1086                     }
1087                     else
1088                     {
1089                         // output texture the hard way: XORing out the
1090                         // polygon
1091                         // ===========================================
1092 
1093                         if( !::rtl::math::approxEqual( textures[0].Alpha,
1094                                                        1.0 ) )
1095                         {
1096                             // uh-oh. alpha blending is required,
1097                             // cannot do direct XOR, but have to
1098                             // prepare the filled polygon within a
1099                             // VDev
1100                             VirtualDevice aVDev( rOutDev );
1101                             aVDev.SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
1102 
1103                             // shift output to origin of VDev
1104                             const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
1105                             aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
1106                                                           -aPolygonDeviceRect.Top() ) );
1107 
1108                             const Region aPolyClipRegion( aPolyPoly );
1109 
1110                             aVDev.SetClipRegion( aPolyClipRegion );
1111                             textureFill( aVDev,
1112                                          *pGrfObj,
1113                                          aOutPos,
1114                                          aIntegerNextTileX,
1115                                          aIntegerNextTileY,
1116                                          nTilesX,
1117                                          nTilesY,
1118                                          aSz,
1119                                          aGrfAttr );
1120 
1121                             // output VDev content alpha-blended to
1122                             // target position.
1123                             const ::Point aEmptyPoint;
1124                             Bitmap aContentBmp(
1125                                 aVDev.GetBitmap( aEmptyPoint,
1126                                                  aVDev.GetOutputSizePixel() ) );
1127 
1128                             sal_uInt8 nCol( static_cast< sal_uInt8 >(
1129                                            ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1130                             AlphaMask aAlpha( aVDev.GetOutputSizePixel(),
1131                                               &nCol );
1132 
1133                             BitmapEx aOutputBmpEx( aContentBmp, aAlpha );
1134                             rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1135                                                   aOutputBmpEx );
1136 
1137                             if( mp2ndOutDev )
1138                                 mp2ndOutDev->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1139                                                                        aOutputBmpEx );
1140                         }
1141                         else
1142                         {
1143                             const Region aPolyClipRegion( aPolyPoly );
1144 
1145                             rOutDev.Push( PUSH_CLIPREGION );
1146                             rOutDev.SetClipRegion( aPolyClipRegion );
1147 
1148                             textureFill( rOutDev,
1149                                          *pGrfObj,
1150                                          aPt,
1151                                          aIntegerNextTileX,
1152                                          aIntegerNextTileY,
1153                                          nTilesX,
1154                                          nTilesY,
1155                                          aSz,
1156                                          aGrfAttr );
1157                             rOutDev.Pop();
1158 
1159                             if( mp2ndOutDev )
1160                             {
1161                                 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1162                                 r2ndOutDev.Push( PUSH_CLIPREGION );
1163 
1164                                 r2ndOutDev.SetClipRegion( aPolyClipRegion );
1165                                 textureFill( r2ndOutDev,
1166                                              *pGrfObj,
1167                                              aPt,
1168                                              aIntegerNextTileX,
1169                                              aIntegerNextTileY,
1170                                              nTilesX,
1171                                              nTilesY,
1172                                              aSz,
1173                                              aGrfAttr );
1174                                 r2ndOutDev.Pop();
1175                             }
1176                         }
1177                     }
1178                 }
1179             }
1180         }
1181 
1182         // TODO(P1): Provide caching here.
1183         return uno::Reference< rendering::XCachedPrimitive >(NULL);
1184     }
1185 
1186 }
1187