1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2000, 2010 Oracle and/or its affiliates.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
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.
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).
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.
26 ************************************************************************/
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_canvas.hxx"
31 #include <canvas/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 #include <rtl/math.hxx>
35 #include <com/sun/star/rendering/TexturingMode.hpp>
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>
48 #include <canvas/parametricpolypolygon.hxx>
50 #include "dx_spritecanvas.hxx"
51 #include "dx_canvashelper.hxx"
52 #include "dx_impltools.hxx"
54 #include <boost/scoped_ptr.hpp>
55 #include <boost/bind.hpp>
56 #include <boost/tuple/tuple.hpp>
59 using namespace ::com::sun::star
;
65 typedef ::boost::shared_ptr
< Gdiplus::PathGradientBrush
> PathGradientBrushSharedPtr
;
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
)
74 // setup a linear gradient with given colors
75 // -----------------------------------------
77 Gdiplus::LinearGradientBrush
aBrush(
85 aBrush
.SetInterpolationColors(&rColors
[0],
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() );
94 // TODO(F2): This does not yet support other repeat modes
95 // except clamp, and probably also no multi-texturing
97 // calculate parallelogram of gradient in object space, extend
98 // top and bottom of it such that they cover the whole fill
100 ::basegfx::B2DHomMatrix aTextureTransform
;
101 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform
,
102 texture
.AffineTransform
);
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 );
109 aLeftTop
*= aTextureTransform
;
110 aLeftBottom
*= aTextureTransform
;
111 aRightTop
*= aTextureTransform
;
112 aRightBottom
*= aTextureTransform
;
114 Gdiplus::RectF aBounds
;
115 rFillPath
->GetBounds( &aBounds
, NULL
, NULL
);
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
,
124 tools::b2dRangeFromGdiPlusRectF( aBounds
) );
126 // calc length of bound rect diagonal
127 const double nDiagonalLength(
128 hypot( aBounds
.Width
,
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
;
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
);
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();
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
) )
165 Gdiplus::SolidBrush
aBackgroundBrush2( rColors
.back() );
166 rGraphics
->FillPath( &aBackgroundBrush2
, &aSolidFillPath
);
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();
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
) )
190 // now, finally, output the gradient
191 Gdiplus::Matrix aMatrix
;
192 tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix
,
193 texture
.AffineTransform
);
194 aBrush
.SetTransform( &aMatrix
);
196 rGraphics
->FillRectangle( &aBrush
, aBounds
);
201 int numColorSteps( const Gdiplus::Color
& rColor1
, const Gdiplus::Color
& rColor2
)
204 labs( rColor1
.GetRed() - rColor2
.GetRed() ),
206 labs( rColor1
.GetGreen() - rColor2
.GetGreen() ),
207 labs( rColor1
.GetBlue() - rColor2
.GetBlue() ) ) );
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
)
219 // copy original fill path object, might have to change it
221 GraphicsPathSharedPtr
pFillPath( rPath
);
222 const ::basegfx::B2DPolygon
& rGradientPoly( rValues
.maGradientPoly
);
224 PathGradientBrushSharedPtr pGradientBrush
;
226 // fill background uniformly with end color
227 Gdiplus::SolidBrush
aBackgroundBrush( rColors
[0] );
228 rGraphics
->FillPath( &aBackgroundBrush
, pFillPath
.get() );
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
,
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.
247 // TODO(F2): This does not yet support other repeat modes
248 // except clamp, and probably also no multi-texturing
250 // limit output to to-be-filled polygon
251 if( Gdiplus::Ok
!= rGraphics
->SetClip( pFillPath
.get(),
252 Gdiplus::CombineModeIntersect
) )
257 // disable anti-aliasing, if any
258 const Gdiplus::SmoothingMode
eOldAAMode( rGraphics
->GetSmoothingMode() );
259 rGraphics
->SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed
);
262 // determine number of steps to use
263 // --------------------------------
265 // TODO(Q2): Unify step calculations with VCL canvas
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
,
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
;
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
;
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
);
296 aInnerPoly
= aOuterPoly
;
297 aOuterPoly
.transform(aTextureTransform
);
300 // apply scaling (possibly anisotrophic) to inner polygon
301 // ------------------------------------------------------
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 )
311 // width > height case
312 aInnerPolygonTransformMatrix
.scale( 1.0 - 1.0/nAspectRatio
,
315 else if( nAspectRatio
< 1.0 )
317 // width < height case
318 aInnerPolygonTransformMatrix
.scale( 0.0,
319 1.0 - nAspectRatio
);
324 aInnerPolygonTransformMatrix
.scale( 0.0, 0.0 );
327 // and finally, add texture transform to it.
328 aInnerPolygonTransformMatrix
*= aTextureTransform
;
330 // apply final matrix to polygon
331 aInnerPoly
.transform( aInnerPolygonTransformMatrix
);
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
)
339 std::ptrdiff_t nIndex
;
341 const double fT( i
/double(nStepCount
) );
342 boost::tuples::tie(nIndex
,fAlpha
)=aLerper
.lerp(fT
);
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
) ) );
349 aFillBrush
.SetColor( aFillColor
);
350 aCurrPath
.Reset(); aCurrPath
.StartFigure();
351 for( unsigned int p
=1; p
<nNumPoints
; ++p
)
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
) );
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()));
364 aCurrPath
.CloseFigure();
366 rGraphics
->FillPath( &aFillBrush
, &aCurrPath
);
369 // reset to old anti-alias mode
370 rGraphics
->SetSmoothingMode( eOldAAMode
);
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.
387 tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix
,
388 texture
.AffineTransform
);
389 GraphicsPathSharedPtr
pGradientPath(
390 tools::graphicsPathFromB2DPolygon( rValues
.maGradientPoly
));
391 pGradientPath
->Transform( &aMatrix
);
393 pGradientBrush
.reset(
394 new Gdiplus::PathGradientBrush( pGradientPath
.get() ) );
395 pGradientBrush
->SetInterpolationColors( &rColors
[0],
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
);
407 const bool bTileX( texture
.RepeatModeX
!= rendering::TexturingMode::CLAMP
);
408 const bool bTileY( texture
.RepeatModeY
!= rendering::TexturingMode::CLAMP
);
410 if( bTileX
&& bTileY
)
411 pGradientBrush
->SetWrapMode( Gdiplus::WrapModeTile
);
414 OSL_ENSURE( bTileY
== bTileX
,
415 "ParametricPolyPolygon::fillPolygonalGradient(): Cannot have repeat x and repeat y differ!" );
417 pGradientBrush
->SetWrapMode( Gdiplus::WrapModeClamp
);
420 // render actual gradient
421 rGraphics
->FillPath( pGradientBrush
.get(), pFillPath
.get() );
424 #if defined(VERBOSE) && defined(DBG_UTIL)
425 Gdiplus::Pen
aPen( Gdiplus::Color( 255, 255, 0, 0 ),
428 rGraphics
->DrawRectangle( &aPen
,
429 Gdiplus::RectF( 0.0f
, 0.0f
,
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
)
445 switch( rValues
.meType
)
447 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR
:
448 fillLinearGradient( rGraphics
,
456 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL
:
457 // FALLTHROUGH intended
458 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR
:
459 fillPolygonalGradient( rValues
,
470 ENSURE_OR_THROW( false,
471 "CanvasHelper::fillGradient(): Unexpected case" );
477 void fillBitmap( const uno::Reference
< rendering::XBitmap
>& xBitmap
,
478 GraphicsSharedPtr
& rGraphics
,
479 const GraphicsPathSharedPtr
& rPath
,
480 const rendering::Texture
& rTexture
)
482 OSL_ENSURE( rTexture
.RepeatModeX
==
483 rTexture
.RepeatModeY
,
484 "CanvasHelper::fillBitmap(): GDI+ cannot handle differing X/Y repeat mode." );
486 const bool bClamp( rTexture
.RepeatModeX
== rendering::TexturingMode::NONE
&&
487 rTexture
.RepeatModeY
== rendering::TexturingMode::NONE
);
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" );
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
500 BitmapSharedPtr
pBitmap(
501 tools::bitmapFromXBitmap( xBitmap
) );
503 TextureBrushSharedPtr pBrush
;
504 if( ::rtl::math::approxEqual( rTexture
.Alpha
,
508 new Gdiplus::TextureBrush(
510 bClamp
? Gdiplus::WrapModeClamp
: Gdiplus::WrapModeTile
) );
514 Gdiplus::ImageAttributes aImgAttr
;
516 tools::setModulateImageAttributes( aImgAttr
,
522 Gdiplus::Rect
aRect(0,0,
526 new Gdiplus::TextureBrush(
532 bClamp
? Gdiplus::WrapModeClamp
: Gdiplus::WrapModeTile
);
535 Gdiplus::Matrix aTextureTransform
;
536 tools::gdiPlusMatrixFromAffineMatrix2D( aTextureTransform
,
537 rTexture
.AffineTransform
);
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
) );
545 // TODO(F1): FillRule
547 Gdiplus::Ok
== rGraphics
->FillPath( pBrush
.get(),
549 "CanvasHelper::fillTexturedPolyPolygon(): GDI+ call failed" );
553 // -------------------------------------------------------------
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
)
561 ENSURE_OR_THROW( xPolyPolygon
.is(),
562 "CanvasHelper::fillTexturedPolyPolygon: polygon is NULL");
563 ENSURE_OR_THROW( textures
.getLength(),
564 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
568 GraphicsSharedPtr
pGraphics( mpGraphicsProvider
->getGraphics() );
570 setupGraphicsState( pGraphics
, viewState
, renderState
);
572 // TODO(F1): Multi-texturing
573 if( textures
[0].Gradient
.is() )
575 // try to cast XParametricPolyPolygon2D reference to
576 // our implementation class.
577 ::canvas::ParametricPolyPolygon
* pGradient
=
578 dynamic_cast< ::canvas::ParametricPolyPolygon
* >( textures
[0].Gradient
.get() );
582 const ::canvas::ParametricPolyPolygon::Values
& rValues(
583 pGradient
->getValues() );
585 OSL_ASSERT(rValues
.maColors
.getLength() == rValues
.maStops
.getLength()
586 && rValues
.maColors
.getLength() > 1);
588 std::vector
< Gdiplus::Color
> aColors(rValues
.maColors
.getLength());
589 std::transform(&rValues
.maColors
[0],
590 &rValues
.maColors
[0]+rValues
.maColors
.getLength(),
593 (Gdiplus::ARGB (*)( const uno::Sequence
< double >& ))(
594 &tools::sequenceToArgb
),
596 std::vector
< Gdiplus::REAL
> aStops
;
597 comphelper::sequenceToContainer(aStops
,rValues
.maStops
);
599 // TODO(E1): Return value
600 // TODO(F1): FillRule
601 fillGradient( rValues
,
605 tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon
),
611 else if( textures
[0].Bitmap
.is() )
613 // TODO(E1): Return value
614 // TODO(F1): FillRule
615 fillBitmap( textures
[0].Bitmap
,
617 tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon
),
622 // TODO(P1): Provide caching here.
623 return uno::Reference
< rendering::XCachedPrimitive
>(NULL
);