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