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