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 #include <rtl/math.hxx>
34 
35 #include <com/sun/star/rendering/TexturingMode.hpp>
36 
37 #include <basegfx/matrix/b2dhommatrix.hxx>
38 #include <basegfx/point/b2dpoint.hxx>
39 #include <basegfx/range/b2drectangle.hxx>
40 #include <basegfx/numeric/ftools.hxx>
41 #include <basegfx/polygon/b2dpolygontools.hxx>
42 #include <basegfx/tools/tools.hxx>
43 #include <basegfx/tools/lerp.hxx>
44 #include <basegfx/tools/keystoplerp.hxx>
45 #include <basegfx/tools/canvastools.hxx>
46 #include <basegfx/matrix/b2dhommatrixtools.hxx>
47 
48 #include <canvas/parametricpolypolygon.hxx>
49 
50 #include "dx_spritecanvas.hxx"
51 #include "dx_canvashelper.hxx"
52 #include "dx_impltools.hxx"
53 
54 #include <boost/scoped_ptr.hpp>
55 #include <boost/bind.hpp>
56 #include <boost/tuple/tuple.hpp>
57 
58 
59 using namespace ::com::sun::star;
60 
61 namespace dxcanvas
62 {
63     namespace
64     {
65         typedef ::boost::shared_ptr< Gdiplus::PathGradientBrush > 	PathGradientBrushSharedPtr;
66 
67         bool fillLinearGradient( GraphicsSharedPtr&                             rGraphics,
68                                  const ::canvas::ParametricPolyPolygon::Values& /*rValues*/,
69                                  const std::vector< Gdiplus::Color >&           rColors,
70                                  const std::vector< Gdiplus::REAL >&            rStops,
71                                  const GraphicsPathSharedPtr&                   rFillPath,
72                                  const rendering::Texture&                      texture )
73         {
74             // setup a linear gradient with given colors
75             // -----------------------------------------
76 
77             Gdiplus::LinearGradientBrush aBrush(
78                 Gdiplus::PointF(0.0f,
79                                 0.5f),
80                 Gdiplus::PointF(1.0f,
81                                 0.5f),
82                 rColors[0],
83                 rColors[1] );
84 
85             aBrush.SetInterpolationColors(&rColors[0],
86                                           &rStops[0],
87                                           rColors.size());
88 
89             // render background color, as LinearGradientBrush does not
90             // properly support the WrapModeClamp repeat mode
91             Gdiplus::SolidBrush aBackgroundBrush( rColors[0] );
92             rGraphics->FillPath( &aBackgroundBrush, rFillPath.get() );
93 
94             // TODO(F2): This does not yet support other repeat modes
95             // except clamp, and probably also no multi-texturing
96 
97             // calculate parallelogram of gradient in object space, extend
98             // top and bottom of it such that they cover the whole fill
99             // path bound area
100             ::basegfx::B2DHomMatrix aTextureTransform;
101             ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
102                                                             texture.AffineTransform );
103 
104             ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
105             ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
106             ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
107             ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
108 
109             aLeftTop	*= aTextureTransform;
110             aLeftBottom *= aTextureTransform;
111             aRightTop 	*= aTextureTransform;
112             aRightBottom*= aTextureTransform;
113 
114             Gdiplus::RectF aBounds;
115             rFillPath->GetBounds( &aBounds, NULL, NULL );
116 
117             // now, we potentially have to enlarge our gradient area
118             // atop and below the transformed [0,1]x[0,1] unit rect,
119             // for the gradient to fill the complete bound rect.
120             ::basegfx::tools::infiniteLineFromParallelogram( aLeftTop,
121                                                              aLeftBottom,
122                                                              aRightTop,
123                                                              aRightBottom,
124                                                              tools::b2dRangeFromGdiPlusRectF( aBounds ) );
125 
126             // calc length of bound rect diagonal
127             const double nDiagonalLength(
128                 hypot( aBounds.Width,
129                        aBounds.Height ) );
130 
131             // generate a path which covers the 'right' side of the
132             // gradient, extending two times the bound rect diagonal to
133             // the right (and thus covering the whole half plane 'right'
134             // of the gradient). Take the middle of the gradient as the
135             // 'left' side of the polygon, to not fall victim to rounding
136             // errors at the edge.
137             ::basegfx::B2DVector aDirection( aLeftTop - aLeftBottom );
138             aDirection = ::basegfx::getNormalizedPerpendicular( aDirection );
139             aDirection *= nDiagonalLength;
140 
141             const ::basegfx::B2DPoint aHalfPlaneLeftTop( (aLeftTop + aRightTop) * 0.5 );
142             const ::basegfx::B2DPoint aHalfPlaneLeftBottom( (aLeftBottom + aRightBottom) * 0.5 );
143             const ::basegfx::B2DPoint aHalfPlaneRightTop( aRightTop + aDirection );
144             const ::basegfx::B2DPoint aHalfPlaneRightBottom( aRightBottom + aDirection );
145 
146             Gdiplus::GraphicsPath aSolidFillPath;
147             aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getX()),
148                                     static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getY()),
149                                     static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getX()),
150                                     static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getY()) );
151             aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getX()),
152                                     static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getY()),
153                                     static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getX()),
154                                     static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getY()) );
155             aSolidFillPath.CloseFigure();
156 
157             // limit output to fill path, we've just generated a path that
158             // might be substantially larger
159             if( Gdiplus::Ok != rGraphics->SetClip( rFillPath.get(),
160                                                    Gdiplus::CombineModeIntersect ) )
161             {
162                 return false;
163             }
164 
165             Gdiplus::SolidBrush aBackgroundBrush2( rColors.back() );
166             rGraphics->FillPath( &aBackgroundBrush2, &aSolidFillPath );
167 
168             // generate clip polygon from the extended parallelogram
169             // (exploit the feature that distinct lines in a figure are
170             // automatically closed by a straight line)
171             Gdiplus::GraphicsPath aClipPath;
172             aClipPath.AddLine( static_cast<Gdiplus::REAL>(aLeftTop.getX()),
173                                static_cast<Gdiplus::REAL>(aLeftTop.getY()),
174                                static_cast<Gdiplus::REAL>(aRightTop.getX()),
175                                static_cast<Gdiplus::REAL>(aRightTop.getY()) );
176             aClipPath.AddLine( static_cast<Gdiplus::REAL>(aRightBottom.getX()),
177                                static_cast<Gdiplus::REAL>(aRightBottom.getY()),
178                                static_cast<Gdiplus::REAL>(aLeftBottom.getX()),
179                                static_cast<Gdiplus::REAL>(aLeftBottom.getY()) );
180             aClipPath.CloseFigure();
181 
182             // limit output to a _single_ strip of the gradient (have to
183             // clip here, since GDI+ wrapmode clamp does not work here)
184             if( Gdiplus::Ok != rGraphics->SetClip( &aClipPath,
185                                                    Gdiplus::CombineModeIntersect ) )
186             {
187                 return false;
188             }
189 
190             // now, finally, output the gradient
191             Gdiplus::Matrix aMatrix;
192             tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix,
193                                                     texture.AffineTransform );
194             aBrush.SetTransform( &aMatrix );
195 
196             rGraphics->FillRectangle( &aBrush, aBounds );
197 
198             return true;
199         }
200 
201         int numColorSteps( const Gdiplus::Color& rColor1, const Gdiplus::Color& rColor2 )
202         {
203             return ::std::max(
204                 labs( rColor1.GetRed() - rColor2.GetRed() ),
205                 ::std::max(
206                     labs( rColor1.GetGreen() - rColor2.GetGreen() ),
207                     labs( rColor1.GetBlue()  - rColor2.GetBlue() ) ) );
208         }
209 
210         bool fillPolygonalGradient( const ::canvas::ParametricPolyPolygon::Values& rValues,
211                                     const std::vector< Gdiplus::Color >&           rColors,
212                                     const std::vector< Gdiplus::REAL >&            rStops,
213                                     GraphicsSharedPtr&                             rGraphics,
214                                     const GraphicsPathSharedPtr&                   rPath,
215                                     const rendering::ViewState& 				   viewState,
216                                     const rendering::RenderState& 				   renderState,
217                                     const rendering::Texture&                      texture )
218         {
219             // copy original fill path object, might have to change it
220             // below
221             GraphicsPathSharedPtr pFillPath( rPath );
222             const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
223 
224             PathGradientBrushSharedPtr pGradientBrush;
225 
226             // fill background uniformly with end color
227             Gdiplus::SolidBrush aBackgroundBrush( rColors[0] );
228             rGraphics->FillPath( &aBackgroundBrush, pFillPath.get() );
229 
230             Gdiplus::Matrix aMatrix;
231             // scale focus according to aspect ratio: for wider-than-tall
232             // bounds (nAspectRatio > 1.0), the focus must have non-zero
233             // width. Specifically, a bound rect twice as wide as tall has
234             // a focus of half it's width.
235             if( !::rtl::math::approxEqual(rValues.mnAspectRatio,
236                                           1.0) )
237             {
238                 // KLUDGE 1:
239                 //
240                 // And here comes the greatest shortcoming of the GDI+
241                 // gradients ever: SetFocusScales completely ignores
242                 // transformations, both when set at the PathGradientBrush
243                 // and for the world coordinate system. Thus, to correctly
244                 // display anisotrophic path gradients, we have to render
245                 // them by hand. WTF.
246 
247                 // TODO(F2): This does not yet support other repeat modes
248                 // except clamp, and probably also no multi-texturing
249 
250                 // limit output to to-be-filled polygon
251                 if( Gdiplus::Ok != rGraphics->SetClip( pFillPath.get(),
252                                                        Gdiplus::CombineModeIntersect ) )
253                 {
254                     return false;
255                 }
256 
257                 // disable anti-aliasing, if any
258                 const Gdiplus::SmoothingMode eOldAAMode( rGraphics->GetSmoothingMode() );
259                 rGraphics->SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed );
260 
261 
262                 // determine number of steps to use
263                 // --------------------------------
264 
265                 // TODO(Q2): Unify step calculations with VCL canvas
266                 int nColorSteps = 0;
267                 for( size_t i=0; i<rColors.size()-1; ++i )
268                     nColorSteps += numColorSteps(rColors[i],rColors[i+1]);
269                 ::basegfx::B2DHomMatrix aTotalTransform;
270                 const int nStepCount=
271                     ::canvas::tools::calcGradientStepCount(aTotalTransform,
272                                                            viewState,
273                                                            renderState,
274                                                            texture,
275                                                            nColorSteps);
276 
277                 ::basegfx::B2DHomMatrix aTextureTransform;
278                 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
279                                                                 texture.AffineTransform );
280                 // determine overall transformation for inner polygon (might
281                 // have to be prefixed by anisotrophic scaling)
282                 ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
283 
284                 // For performance reasons, we create a temporary VCL polygon
285                 // here, keep it all the way and only change the vertex values
286                 // in the loop below (as ::Polygon is a pimpl class, creating
287                 // one every loop turn would really stress the mem allocator)
288                 ::basegfx::B2DPolygon 	aOuterPoly( rGradientPoly );
289                 ::basegfx::B2DPolygon 	aInnerPoly;
290 
291                 // subdivide polygon _before_ rendering, would otherwise have
292                 // to be performed on every loop turn.
293                 if( aOuterPoly.areControlPointsUsed() )
294                     aOuterPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aOuterPoly);
295 
296                 aInnerPoly = aOuterPoly;
297                 aOuterPoly.transform(aTextureTransform);
298 
299 
300                 // apply scaling (possibly anisotrophic) to inner polygon
301                 // ------------------------------------------------------
302 
303                 // scale inner polygon according to aspect ratio: for
304                 // wider-than-tall bounds (nAspectRatio > 1.0), the inner
305                 // polygon, representing the gradient focus, must have
306                 // non-zero width. Specifically, a bound rect twice as wide as
307                 // tall has a focus polygon of half it's width.
308                 const double nAspectRatio( rValues.mnAspectRatio );
309                 if( nAspectRatio > 1.0 )
310                 {
311                     // width > height case
312                     aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
313                                                         0.0 );
314                 }
315                 else if( nAspectRatio < 1.0 )
316                 {
317                     // width < height case
318                     aInnerPolygonTransformMatrix.scale( 0.0,
319                                                         1.0 - nAspectRatio );
320                 }
321                 else
322                 {
323                     // isotrophic case
324                     aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
325                 }
326 
327                 // and finally, add texture transform to it.
328                 aInnerPolygonTransformMatrix *= aTextureTransform;
329 
330                 // apply final matrix to polygon
331                 aInnerPoly.transform( aInnerPolygonTransformMatrix );
332 
333                 Gdiplus::GraphicsPath aCurrPath;
334                 Gdiplus::SolidBrush   aFillBrush( rColors[0] );
335                 const sal_uInt32      nNumPoints( aOuterPoly.count() );
336                 basegfx::tools::KeyStopLerp aLerper(rValues.maStops);
337                 for( int i=1; i<nStepCount; ++i )
338                 {
339                     std::ptrdiff_t nIndex;
340                     double fAlpha;
341                     const double fT( i/double(nStepCount) );
342                     boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT);
343 
344                     const Gdiplus::Color aFillColor(
345                         static_cast<BYTE>( basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha) ),
346                         static_cast<BYTE>( basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha) ),
347                         static_cast<BYTE>( basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha) ) );
348 
349                     aFillBrush.SetColor( aFillColor );
350                     aCurrPath.Reset(); aCurrPath.StartFigure();
351                     for( unsigned int p=1; p<nNumPoints; ++p )
352                     {
353                         const ::basegfx::B2DPoint& rOuterPoint1( aOuterPoly.getB2DPoint(p-1) );
354                         const ::basegfx::B2DPoint& rInnerPoint1( aInnerPoly.getB2DPoint(p-1) );
355                         const ::basegfx::B2DPoint& rOuterPoint2( aOuterPoly.getB2DPoint(p) );
356                         const ::basegfx::B2DPoint& rInnerPoint2( aInnerPoly.getB2DPoint(p) );
357 
358                         aCurrPath.AddLine(
359                             Gdiplus::REAL(fT*rInnerPoint1.getX() + (1-fT)*rOuterPoint1.getX()),
360                             Gdiplus::REAL(fT*rInnerPoint1.getY() + (1-fT)*rOuterPoint1.getY()),
361                             Gdiplus::REAL(fT*rInnerPoint2.getX() + (1-fT)*rOuterPoint2.getX()),
362                             Gdiplus::REAL(fT*rInnerPoint2.getY() + (1-fT)*rOuterPoint2.getY()));
363                     }
364                     aCurrPath.CloseFigure();
365 
366                     rGraphics->FillPath( &aFillBrush, &aCurrPath );
367                 }
368 
369                 // reset to old anti-alias mode
370                 rGraphics->SetSmoothingMode( eOldAAMode );
371             }
372             else
373             {
374                 // KLUDGE 2:
375                 //
376                 // We're generating a PathGradientBrush from scratch here,
377                 // and put in a transformed GraphicsPath (transformed with
378                 // the texture transform). This is because the
379                 // straight-forward approach to store a Brush pointer at
380                 // this class and set a texture transform via
381                 // PathGradientBrush::SetTransform() is spoiled by MS: it
382                 // seems that _either_ the texture transform, _or_ the
383                 // transform at the Graphics can be set, but not both. If
384                 // one sets both, only the translational components of the
385                 // texture is respected.
386 
387                 tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix,
388                                                         texture.AffineTransform );
389                 GraphicsPathSharedPtr pGradientPath(
390                     tools::graphicsPathFromB2DPolygon( rValues.maGradientPoly ));
391                 pGradientPath->Transform( &aMatrix );
392 
393                 pGradientBrush.reset(
394                     new Gdiplus::PathGradientBrush( pGradientPath.get() ) );
395                 pGradientBrush->SetInterpolationColors( &rColors[0],
396                                                         &rStops[0],
397                                                         rStops.size() );
398 
399                 // explicitely setup center point. Since the center of GDI+
400                 // gradients are by default the _centroid_ of the path
401                 // (i.e. the weighted sum of edge points), it will not
402                 // necessarily coincide with our notion of center.
403                 Gdiplus::PointF aCenterPoint(0, 0);
404                 aMatrix.TransformPoints( &aCenterPoint );
405                 pGradientBrush->SetCenterPoint( aCenterPoint );
406 
407                 const bool bTileX( texture.RepeatModeX != rendering::TexturingMode::CLAMP );
408                 const bool bTileY( texture.RepeatModeY != rendering::TexturingMode::CLAMP );
409 
410                 if( bTileX && bTileY )
411                     pGradientBrush->SetWrapMode( Gdiplus::WrapModeTile );
412                 else
413                 {
414                     OSL_ENSURE( bTileY == bTileX,
415                                 "ParametricPolyPolygon::fillPolygonalGradient(): Cannot have repeat x and repeat y differ!" );
416 
417                     pGradientBrush->SetWrapMode( Gdiplus::WrapModeClamp );
418                 }
419 
420                 // render actual gradient
421                 rGraphics->FillPath( pGradientBrush.get(), pFillPath.get() );
422             }
423 
424 #if defined(VERBOSE) && defined(DBG_UTIL)
425             Gdiplus::Pen aPen( Gdiplus::Color( 255, 255, 0, 0 ),
426                                0.0001f );
427 
428             rGraphics->DrawRectangle( &aPen,
429                                       Gdiplus::RectF( 0.0f, 0.0f,
430                                                       1.0f, 1.0f ) );
431 #endif
432 
433             return true;
434         }
435 
436         bool fillGradient( const ::canvas::ParametricPolyPolygon::Values& rValues,
437                            const std::vector< Gdiplus::Color >&           rColors,
438                            const std::vector< Gdiplus::REAL >&            rStops,
439                            GraphicsSharedPtr&                             rGraphics,
440                            const GraphicsPathSharedPtr&                   rPath,
441                            const rendering::ViewState& 					  viewState,
442                            const rendering::RenderState& 				  renderState,
443                            const rendering::Texture&                      texture )
444         {
445             switch( rValues.meType )
446             {
447                 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR:
448                     fillLinearGradient( rGraphics,
449                                         rValues,
450                                         rColors,
451                                         rStops,
452                                         rPath,
453                                         texture  );
454                     break;
455 
456                 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL:
457                     // FALLTHROUGH intended
458                 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR:
459                     fillPolygonalGradient( rValues,
460                                            rColors,
461                                            rStops,
462                                            rGraphics,
463                                            rPath,
464                                            viewState,
465                                            renderState,
466                                            texture );
467                     break;
468 
469                 default:
470                     ENSURE_OR_THROW( false,
471                                       "CanvasHelper::fillGradient(): Unexpected case" );
472             }
473 
474             return true;
475         }
476 
477         void fillBitmap( const uno::Reference< rendering::XBitmap >& xBitmap,
478                          GraphicsSharedPtr&                          rGraphics,
479                          const GraphicsPathSharedPtr&                rPath,
480                          const rendering::Texture&                   rTexture )
481         {
482             OSL_ENSURE( rTexture.RepeatModeX ==
483                         rTexture.RepeatModeY,
484                         "CanvasHelper::fillBitmap(): GDI+ cannot handle differing X/Y repeat mode." );
485 
486             const bool bClamp( rTexture.RepeatModeX == rendering::TexturingMode::NONE &&
487                                rTexture.RepeatModeY == rendering::TexturingMode::NONE );
488 
489             const geometry::IntegerSize2D aBmpSize( xBitmap->getSize() );
490             ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
491                                  aBmpSize.Height != 0,
492                                  "CanvasHelper::fillBitmap(): zero-sized texture bitmap" );
493 
494             // TODO(P3): Detect case that path is rectangle and
495             // bitmap is just scaled into that. Then, we can
496             // render directly, without generating a temporary
497             // GDI+ bitmap (this is significant, because drawing
498             // layer presents background object bitmap in that
499             // way!)
500             BitmapSharedPtr pBitmap(
501                 tools::bitmapFromXBitmap( xBitmap ) );
502 
503             TextureBrushSharedPtr pBrush;
504             if( ::rtl::math::approxEqual( rTexture.Alpha,
505                                           1.0 ) )
506             {
507                 pBrush.reset(
508                     new Gdiplus::TextureBrush(
509                         pBitmap.get(),
510                         bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile ) );
511             }
512             else
513             {
514                 Gdiplus::ImageAttributes aImgAttr;
515 
516                 tools::setModulateImageAttributes( aImgAttr,
517                                                    1.0,
518                                                    1.0,
519                                                    1.0,
520                                                    rTexture.Alpha );
521 
522                 Gdiplus::Rect aRect(0,0,
523                                     aBmpSize.Width,
524                                     aBmpSize.Height);
525                 pBrush.reset(
526                     new Gdiplus::TextureBrush(
527                         pBitmap.get(),
528                         aRect,
529                         &aImgAttr ) );
530 
531                 pBrush->SetWrapMode(
532                     bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile );
533             }
534 
535             Gdiplus::Matrix aTextureTransform;
536             tools::gdiPlusMatrixFromAffineMatrix2D( aTextureTransform,
537                                                     rTexture.AffineTransform );
538 
539             // scale down bitmap to [0,1]x[0,1] rect, as required
540             // from the XCanvas interface.
541             pBrush->MultiplyTransform( &aTextureTransform );
542             pBrush->ScaleTransform( static_cast< Gdiplus::REAL >(1.0/aBmpSize.Width),
543                                     static_cast< Gdiplus::REAL >(1.0/aBmpSize.Height) );
544 
545             // TODO(F1): FillRule
546             ENSURE_OR_THROW(
547                 Gdiplus::Ok == rGraphics->FillPath( pBrush.get(),
548                                                     rPath.get() ),
549                 "CanvasHelper::fillTexturedPolyPolygon(): GDI+ call failed" );
550         }
551     }
552 
553     // -------------------------------------------------------------
554 
555     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
556                                                                                          const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
557                                                                                          const rendering::ViewState& 						viewState,
558                                                                                          const rendering::RenderState& 						renderState,
559                                                                                          const uno::Sequence< rendering::Texture >& 		textures )
560     {
561         ENSURE_OR_THROW( xPolyPolygon.is(),
562                           "CanvasHelper::fillTexturedPolyPolygon: polygon is NULL");
563         ENSURE_OR_THROW( textures.getLength(),
564                           "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
565 
566         if( needOutput() )
567         {
568 			GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
569 
570 			setupGraphicsState( pGraphics, viewState, renderState );
571 
572             // TODO(F1): Multi-texturing
573             if( textures[0].Gradient.is() )
574             {
575                 // try to cast XParametricPolyPolygon2D reference to
576                 // our implementation class.
577                 ::canvas::ParametricPolyPolygon* pGradient =
578                       dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
579 
580                 if( pGradient )
581                 {
582                     const ::canvas::ParametricPolyPolygon::Values& rValues(
583                         pGradient->getValues() );
584 
585                     OSL_ASSERT(rValues.maColors.getLength() == rValues.maStops.getLength()
586                                && rValues.maColors.getLength() > 1);
587 
588                     std::vector< Gdiplus::Color > aColors(rValues.maColors.getLength());
589                     std::transform(&rValues.maColors[0],
590                                    &rValues.maColors[0]+rValues.maColors.getLength(),
591                                    aColors.begin(),
592                                    boost::bind(
593                                        (Gdiplus::ARGB (*)( const uno::Sequence< double >& ))(
594                                            &tools::sequenceToArgb),
595                                        _1));
596                     std::vector< Gdiplus::REAL > aStops;
597                     comphelper::sequenceToContainer(aStops,rValues.maStops);
598 
599                     // TODO(E1): Return value
600                     // TODO(F1): FillRule
601                     fillGradient( rValues,
602                                   aColors,
603                                   aStops,
604                                   pGraphics,
605                                   tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ),
606                                   viewState,
607                                   renderState,
608                                   textures[0] );
609                 }
610             }
611             else if( textures[0].Bitmap.is() )
612             {
613                 // TODO(E1): Return value
614                 // TODO(F1): FillRule
615                 fillBitmap( textures[0].Bitmap,
616                             pGraphics,
617                             tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ),
618                             textures[0] );
619             }
620         }
621 
622         // TODO(P1): Provide caching here.
623         return uno::Reference< rendering::XCachedPrimitive >(NULL);
624     }
625 }
626