merge the formfield patch from ooo-build
[ooovba.git] / canvas / source / vcl / canvashelper_texturefill.cxx
blob6b8f4dff5a3b9248a218084c9013490fcf64cf18
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: canvashelper_texturefill.cxx,v $
10 * $Revision: 1.13 $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_canvas.hxx"
34 #include <canvas/debug.hxx>
35 #include <tools/diagnose_ex.h>
37 #include <rtl/math.hxx>
39 #include <com/sun/star/rendering/TextDirection.hpp>
40 #include <com/sun/star/rendering/TexturingMode.hpp>
41 #include <com/sun/star/rendering/PathCapType.hpp>
42 #include <com/sun/star/rendering/PathJoinType.hpp>
44 #include <tools/poly.hxx>
45 #include <vcl/window.hxx>
46 #include <vcl/bitmapex.hxx>
47 #include <vcl/bmpacc.hxx>
48 #include <vcl/virdev.hxx>
49 #include <vcl/canvastools.hxx>
51 #include <basegfx/matrix/b2dhommatrix.hxx>
52 #include <basegfx/range/b2drectangle.hxx>
53 #include <basegfx/point/b2dpoint.hxx>
54 #include <basegfx/vector/b2dsize.hxx>
55 #include <basegfx/polygon/b2dpolygon.hxx>
56 #include <basegfx/polygon/b2dpolygontools.hxx>
57 #include <basegfx/polygon/b2dpolypolygontools.hxx>
58 #include <basegfx/polygon/b2dlinegeometry.hxx>
59 #include <basegfx/tools/tools.hxx>
60 #include <basegfx/tools/canvastools.hxx>
61 #include <basegfx/numeric/ftools.hxx>
63 #include <comphelper/sequence.hxx>
65 #include <canvas/canvastools.hxx>
66 #include <canvas/parametricpolypolygon.hxx>
68 #include "spritecanvas.hxx"
69 #include "canvashelper.hxx"
70 #include "impltools.hxx"
73 using namespace ::com::sun::star;
75 namespace vclcanvas
77 namespace
79 bool textureFill( OutputDevice& rOutDev,
80 GraphicObject& rGraphic,
81 const ::Point& rPosPixel,
82 const ::Size& rNextTileX,
83 const ::Size& rNextTileY,
84 sal_Int32 nTilesX,
85 sal_Int32 nTilesY,
86 const ::Size& rTileSize,
87 const GraphicAttr& rAttr)
89 BOOL bRet( false );
90 Point aCurrPos;
91 int nX, nY;
93 for( nY=0; nY < nTilesY; ++nY )
95 aCurrPos.X() = rPosPixel.X() + nY*rNextTileY.Width();
96 aCurrPos.Y() = rPosPixel.Y() + nY*rNextTileY.Height();
98 for( nX=0; nX < nTilesX; ++nX )
100 // update return value. This method should return true, if
101 // at least one of the looped Draws succeeded.
102 bRet |= rGraphic.Draw( &rOutDev,
103 aCurrPos,
104 rTileSize,
105 &rAttr );
107 aCurrPos.X() += rNextTileX.Width();
108 aCurrPos.Y() += rNextTileX.Height();
112 return bRet;
116 /** Fill linear or axial gradient
118 Since most of the code for linear and axial gradients are
119 the same, we've a unified method here
121 void fillGeneralLinearGradient( OutputDevice& rOutDev,
122 const ::basegfx::B2DHomMatrix& rTextureTransform,
123 const ::Rectangle& rBounds,
124 int nStepCount,
125 const ::Color& rColor1,
126 const ::Color& rColor2,
127 bool bFillNonOverlapping,
128 bool bAxialGradient )
130 (void)bFillNonOverlapping;
132 // determine general position of gradient in relation to
133 // the bound rect
134 // =====================================================
136 ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
137 ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
138 ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
139 ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
141 aLeftTop *= rTextureTransform;
142 aLeftBottom *= rTextureTransform;
143 aRightTop *= rTextureTransform;
144 aRightBottom*= rTextureTransform;
146 // calc length of bound rect diagonal
147 const ::basegfx::B2DVector aBoundRectDiagonal(
148 ::vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) -
149 ::vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) );
150 const double nDiagonalLength( aBoundRectDiagonal.getLength() );
152 // create direction of gradient:
153 // _______
154 // | | |
155 // -> | | | ...
156 // | | |
157 // -------
158 ::basegfx::B2DVector aDirection( aRightTop - aLeftTop );
159 aDirection.normalize();
161 // now, we potentially have to enlarge our gradient area
162 // atop and below the transformed [0,1]x[0,1] unit rect,
163 // for the gradient to fill the complete bound rect.
164 ::basegfx::tools::infiniteLineFromParallelogram( aLeftTop,
165 aLeftBottom,
166 aRightTop,
167 aRightBottom,
168 ::vcl::unotools::b2DRectangleFromRectangle( rBounds ) );
171 // render gradient
172 // ===============
174 // for linear gradients, it's easy to render
175 // non-overlapping polygons: just split the gradient into
176 // nStepCount small strips. Prepare the strip now.
178 // For performance reasons, we create a temporary VCL
179 // polygon here, keep it all the way and only change the
180 // vertex values in the loop below (as ::Polygon is a
181 // pimpl class, creating one every loop turn would really
182 // stress the mem allocator)
183 ::Polygon aTempPoly( static_cast<USHORT>(5) );
185 OSL_ENSURE( nStepCount >= 3,
186 "fillLinearGradient(): stepcount smaller than 3" );
189 // fill initial strip (extending two times the bound rect's
190 // diagonal to the 'left'
191 // ------------------------------------------------------
193 // calculate left edge, by moving left edge of the
194 // gradient rect two times the bound rect's diagonal to
195 // the 'left'. Since we postpone actual rendering into the
196 // loop below, we set the _right_ edge here, which will be
197 // readily copied into the left edge in the loop below
198 const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
199 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ),
200 ::basegfx::fround( rPoint1.getY() ) );
202 const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection );
203 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ),
204 ::basegfx::fround( rPoint2.getY() ) );
207 // iteratively render all other strips
208 // -----------------------------------
210 // ensure that nStepCount is odd, to have a well-defined
211 // middle index for axial gradients.
212 if( bAxialGradient && !(nStepCount % 2) )
213 ++nStepCount;
215 const int nStepCountHalved( nStepCount / 2 );
217 // only iterate nStepCount-1 steps, as the last strip is
218 // explicitely painted below
219 for( int i=0; i<nStepCount-1; ++i )
221 // lerp color
222 if( bAxialGradient )
224 // axial gradient has a triangle-like interpolation function
225 const int iPrime( i<=nStepCountHalved ? i : nStepCount-i-1);
227 rOutDev.SetFillColor(
228 Color( (UINT8)(((nStepCountHalved - iPrime)*rColor1.GetRed() + iPrime*rColor2.GetRed())/nStepCountHalved),
229 (UINT8)(((nStepCountHalved - iPrime)*rColor1.GetGreen() + iPrime*rColor2.GetGreen())/nStepCountHalved),
230 (UINT8)(((nStepCountHalved - iPrime)*rColor1.GetBlue() + iPrime*rColor2.GetBlue())/nStepCountHalved) ) );
232 else
234 // linear gradient has a plain lerp between start and end color
235 rOutDev.SetFillColor(
236 Color( (UINT8)(((nStepCount - i)*rColor1.GetRed() + i*rColor2.GetRed())/nStepCount),
237 (UINT8)(((nStepCount - i)*rColor1.GetGreen() + i*rColor2.GetGreen())/nStepCount),
238 (UINT8)(((nStepCount - i)*rColor1.GetBlue() + i*rColor2.GetBlue())/nStepCount) ) );
241 // copy right egde of polygon to left edge (and also
242 // copy the closing point)
243 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
244 aTempPoly[3] = aTempPoly[2];
246 // calculate new right edge, from interpolating
247 // between start and end line. Note that i is
248 // increased by one, to account for the fact that we
249 // calculate the right border here (whereas the fill
250 // color is governed by the left edge)
251 const ::basegfx::B2DPoint& rPoint3(
252 (nStepCount - i-1)/double(nStepCount)*aLeftTop +
253 (i+1)/double(nStepCount)*aRightTop );
254 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ),
255 ::basegfx::fround( rPoint3.getY() ) );
257 const ::basegfx::B2DPoint& rPoint4(
258 (nStepCount - i-1)/double(nStepCount)*aLeftBottom +
259 (i+1)/double(nStepCount)*aRightBottom );
260 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ),
261 ::basegfx::fround( rPoint4.getY() ) );
263 rOutDev.DrawPolygon( aTempPoly );
266 // fill final strip (extending two times the bound rect's
267 // diagonal to the 'right'
268 // ------------------------------------------------------
270 // copy right egde of polygon to left edge (and also
271 // copy the closing point)
272 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
273 aTempPoly[3] = aTempPoly[2];
275 // calculate new right edge, by moving right edge of the
276 // gradient rect two times the bound rect's diagonal to
277 // the 'right'.
278 const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
279 aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ),
280 ::basegfx::fround( rPoint3.getY() ) );
282 const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection );
283 aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ),
284 ::basegfx::fround( rPoint4.getY() ) );
286 if( bAxialGradient )
287 rOutDev.SetFillColor( rColor1 );
288 else
289 rOutDev.SetFillColor( rColor2 );
291 rOutDev.DrawPolygon( aTempPoly );
295 inline void fillLinearGradient( OutputDevice& rOutDev,
296 const ::Color& rColor1,
297 const ::Color& rColor2,
298 const ::basegfx::B2DHomMatrix& rTextureTransform,
299 const ::Rectangle& rBounds,
300 int nStepCount,
301 bool bFillNonOverlapping )
303 fillGeneralLinearGradient( rOutDev,
304 rTextureTransform,
305 rBounds,
306 nStepCount,
307 rColor1,
308 rColor2,
309 bFillNonOverlapping,
310 false );
313 inline void fillAxialGradient( OutputDevice& rOutDev,
314 const ::Color& rColor1,
315 const ::Color& rColor2,
316 const ::basegfx::B2DHomMatrix& rTextureTransform,
317 const ::Rectangle& rBounds,
318 int nStepCount,
319 bool bFillNonOverlapping )
321 fillGeneralLinearGradient( rOutDev,
322 rTextureTransform,
323 rBounds,
324 nStepCount,
325 rColor1,
326 rColor2,
327 bFillNonOverlapping,
328 true );
331 void fillPolygonalGradient( OutputDevice& rOutDev,
332 const ::canvas::ParametricPolyPolygon::Values& rValues,
333 const ::Color& rColor1,
334 const ::Color& rColor2,
335 const ::basegfx::B2DHomMatrix& rTextureTransform,
336 const ::Rectangle& rBounds,
337 int nStepCount,
338 bool bFillNonOverlapping )
340 const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
342 ENSURE_OR_THROW( rGradientPoly.count() > 2,
343 "fillPolygonalGradient(): polygon without area given" );
345 // For performance reasons, we create a temporary VCL polygon
346 // here, keep it all the way and only change the vertex values
347 // in the loop below (as ::Polygon is a pimpl class, creating
348 // one every loop turn would really stress the mem allocator)
349 ::basegfx::B2DPolygon aOuterPoly( rGradientPoly );
350 ::basegfx::B2DPolygon aInnerPoly;
352 // subdivide polygon _before_ rendering, would otherwise have
353 // to be performed on every loop turn.
354 if( aOuterPoly.areControlPointsUsed() )
355 aOuterPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aOuterPoly);
357 aInnerPoly = aOuterPoly;
359 // only transform outer polygon _after_ copying it into
360 // aInnerPoly, because inner polygon has to be scaled before
361 // the actual texture transformation takes place
362 aOuterPoly.transform( rTextureTransform );
364 // determine overall transformation for inner polygon (might
365 // have to be prefixed by anisotrophic scaling)
366 ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
369 // apply scaling (possibly anisotrophic) to inner polygon
370 // ------------------------------------------------------
372 // move center of scaling to origin
373 aInnerPolygonTransformMatrix.translate( -0.5, -0.5 );
375 // scale inner polygon according to aspect ratio: for
376 // wider-than-tall bounds (nAspectRatio > 1.0), the inner
377 // polygon, representing the gradient focus, must have
378 // non-zero width. Specifically, a bound rect twice as wide as
379 // tall has a focus polygon of half it's width.
380 const double nAspectRatio( rValues.mnAspectRatio );
381 if( nAspectRatio > 1.0 )
383 // width > height case
384 aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
385 0.0 );
387 else if( nAspectRatio < 1.0 )
389 // width < height case
390 aInnerPolygonTransformMatrix.scale( 0.0,
391 1.0 - nAspectRatio );
393 else
395 // isotrophic case
396 aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
399 // move origin back to former center of polygon
400 aInnerPolygonTransformMatrix.translate( 0.5, 0.5 );
402 // and finally, add texture transform to it.
403 aInnerPolygonTransformMatrix *= rTextureTransform;
405 // apply final matrix to polygon
406 aInnerPoly.transform( aInnerPolygonTransformMatrix );
409 const sal_Int32 nNumPoints( aOuterPoly.count() );
410 ::Polygon aTempPoly( static_cast<USHORT>(nNumPoints+1) );
412 // increase number of steps by one: polygonal gradients have
413 // the outermost polygon rendered in rColor2, and the
414 // innermost in rColor1. The innermost polygon will never
415 // have zero area, thus, we must divide the interval into
416 // nStepCount+1 steps. For example, to create 3 steps:
418 // | |
419 // |-------|-------|-------|
420 // | |
421 // 3 2 1 0
423 // This yields 4 tick marks, where 0 is never attained (since
424 // zero-area polygons typically don't display perceivable
425 // color).
426 ++nStepCount;
428 if( !bFillNonOverlapping )
430 // fill background
431 rOutDev.SetFillColor( rColor1 );
432 rOutDev.DrawRect( rBounds );
434 // render polygon
435 // ==============
437 for( int i=1,p; i<nStepCount; ++i )
439 // lerp color
440 rOutDev.SetFillColor(
441 Color( (UINT8)(((nStepCount - i)*rColor1.GetRed() + i*rColor2.GetRed())/nStepCount),
442 (UINT8)(((nStepCount - i)*rColor1.GetGreen() + i*rColor2.GetGreen())/nStepCount),
443 (UINT8)(((nStepCount - i)*rColor1.GetBlue() + i*rColor2.GetBlue())/nStepCount) ) );
445 // scale and render polygon, by interpolating between
446 // outer and inner polygon.
448 // calc interpolation parameter in [0,1] range
449 const double nT( (nStepCount-i)/double(nStepCount) );
451 for( p=0; p<nNumPoints; ++p )
453 const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
454 const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
456 aTempPoly[(USHORT)p] = ::Point(
457 basegfx::fround( (1.0-nT)*rInnerPoint.getX() + nT*rOuterPoint.getX() ),
458 basegfx::fround( (1.0-nT)*rInnerPoint.getY() + nT*rOuterPoint.getY() ) );
461 // close polygon explicitely
462 aTempPoly[(USHORT)p] = aTempPoly[0];
464 // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
465 // OutputDevice::ImplDrawComplexGradient(), there's a note
466 // that on some VDev's, rendering disjunct poly-polygons
467 // is faster!
468 rOutDev.DrawPolygon( aTempPoly );
471 else
473 // render polygon
474 // ==============
476 // For performance reasons, we create a temporary VCL polygon
477 // here, keep it all the way and only change the vertex values
478 // in the loop below (as ::Polygon is a pimpl class, creating
479 // one every loop turn would really stress the mem allocator)
480 ::PolyPolygon aTempPolyPoly;
481 ::Polygon aTempPoly2( static_cast<USHORT>(nNumPoints+1) );
483 aTempPoly2[0] = rBounds.TopLeft();
484 aTempPoly2[1] = rBounds.TopRight();
485 aTempPoly2[2] = rBounds.BottomRight();
486 aTempPoly2[3] = rBounds.BottomLeft();
487 aTempPoly2[4] = rBounds.TopLeft();
489 aTempPolyPoly.Insert( aTempPoly );
490 aTempPolyPoly.Insert( aTempPoly2 );
492 for( int i=0,p; i<nStepCount; ++i )
494 // lerp color
495 rOutDev.SetFillColor(
496 Color( (UINT8)(((nStepCount - i)*rColor1.GetRed() + i*rColor2.GetRed())/nStepCount),
497 (UINT8)(((nStepCount - i)*rColor1.GetGreen() + i*rColor2.GetGreen())/nStepCount),
498 (UINT8)(((nStepCount - i)*rColor1.GetBlue() + i*rColor2.GetBlue())/nStepCount) ) );
500 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
501 if( i && !(i % 10) )
502 rOutDev.SetFillColor( COL_RED );
503 #endif
505 // scale and render polygon. Note that here, we
506 // calculate the inner polygon, which is actually the
507 // start of the _next_ color strip. Thus, i+1
509 // calc interpolation parameter in [0,1] range
510 const double nT( (nStepCount-i-1)/double(nStepCount) );
512 for( p=0; p<nNumPoints; ++p )
514 const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
515 const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
517 aTempPoly[(USHORT)p] = ::Point(
518 basegfx::fround( (1.0-nT)*rInnerPoint.getX() + nT*rOuterPoint.getX() ),
519 basegfx::fround( (1.0-nT)*rInnerPoint.getY() + nT*rOuterPoint.getY() ) );
522 // close polygon explicitely
523 aTempPoly[(USHORT)p] = aTempPoly[0];
525 // swap inner and outer polygon
526 aTempPolyPoly.Replace( aTempPolyPoly.GetObject( 1 ), 0 );
528 if( i+1<nStepCount )
530 // assign new inner polygon. Note that with this
531 // formulation, the internal pimpl objects for both
532 // temp polygons and the polypolygon remain identical,
533 // minimizing heap accesses (only a Polygon wrapper
534 // object is freed and deleted twice during this swap).
535 aTempPolyPoly.Replace( aTempPoly, 1 );
537 else
539 // last, i.e. inner strip. Now, the inner polygon
540 // has zero area anyway, and to not leave holes in
541 // the gradient, finally render a simple polygon:
542 aTempPolyPoly.Remove( 1 );
545 rOutDev.DrawPolyPolygon( aTempPolyPoly );
550 void doGradientFill( OutputDevice& rOutDev,
551 const ::canvas::ParametricPolyPolygon::Values& rValues,
552 const ::Color& rColor1,
553 const ::Color& rColor2,
554 const ::basegfx::B2DHomMatrix& rTextureTransform,
555 const ::Rectangle& rBounds,
556 int nStepCount,
557 bool bFillNonOverlapping )
559 switch( rValues.meType )
561 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR:
562 fillLinearGradient( rOutDev,
563 rColor1,
564 rColor2,
565 rTextureTransform,
566 rBounds,
567 nStepCount,
568 bFillNonOverlapping );
569 break;
571 case ::canvas::ParametricPolyPolygon::GRADIENT_AXIAL:
572 fillAxialGradient( rOutDev,
573 rColor1,
574 rColor2,
575 rTextureTransform,
576 rBounds,
577 nStepCount,
578 bFillNonOverlapping );
579 break;
581 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL:
582 // FALLTHROUGH intended
583 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR:
584 fillPolygonalGradient( rOutDev,
585 rValues,
586 rColor1,
587 rColor2,
588 rTextureTransform,
589 rBounds,
590 nStepCount,
591 bFillNonOverlapping );
592 break;
594 default:
595 ENSURE_OR_THROW( false,
596 "CanvasHelper::doGradientFill(): Unexpected case" );
600 bool gradientFill( OutputDevice& rOutDev,
601 OutputDevice* p2ndOutDev,
602 const ::canvas::ParametricPolyPolygon::Values& rValues,
603 const ::Color& rColor1,
604 const ::Color& rColor2,
605 const PolyPolygon& rPoly,
606 const rendering::ViewState& viewState,
607 const rendering::RenderState& renderState,
608 const rendering::Texture& texture,
609 int nTransparency )
611 (void)nTransparency;
613 // TODO(T2): It is maybe necessary to lock here, should
614 // maGradientPoly someday cease to be const. But then, beware of
615 // deadlocks, canvashelper calls this method with locked own
616 // mutex.
618 // calculate overall texture transformation (directly from
619 // texture to device space).
620 ::basegfx::B2DHomMatrix aMatrix;
621 ::basegfx::B2DHomMatrix aTextureTransform;
623 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
624 texture.AffineTransform );
625 ::canvas::tools::mergeViewAndRenderTransform(aMatrix,
626 viewState,
627 renderState);
628 aTextureTransform *= aMatrix; // prepend total view/render transformation
630 // determine maximal bound rect of gradient-filled polygon
631 const ::Rectangle aPolygonDeviceRectOrig(
632 rPoly.GetBoundRect() );
634 // determine size of gradient in device coordinate system
635 // (to e.g. determine sensible number of gradient steps)
636 ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
637 ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
638 ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
639 ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
641 aLeftTop *= aTextureTransform;
642 aLeftBottom *= aTextureTransform;
643 aRightTop *= aTextureTransform;
644 aRightBottom*= aTextureTransform;
647 // calc step size
648 // --------------
649 const int nColorSteps(
650 ::std::max(
651 labs( rColor1.GetRed() - rColor2.GetRed() ),
652 ::std::max(
653 labs( rColor1.GetGreen() - rColor2.GetGreen() ),
654 labs( rColor1.GetBlue() - rColor2.GetBlue() ) ) ) );
656 // longest line in gradient bound rect
657 const int nGradientSize(
658 static_cast<int>(
659 ::std::max(
660 ::basegfx::B2DVector(aRightBottom-aLeftTop).getLength(),
661 ::basegfx::B2DVector(aRightTop-aLeftBottom).getLength() ) + 1.0 ) );
663 // typical number for pixel of the same color (strip size)
664 const int nStripSize( nGradientSize < 50 ? 2 : 4 );
666 // use at least three steps, and at utmost the number of color
667 // steps
668 const int nStepCount(
669 ::std::max(
671 ::std::min(
672 nGradientSize / nStripSize,
673 nColorSteps ) ) );
675 rOutDev.SetLineColor();
677 if( tools::isRectangle( rPoly ) )
679 // use optimized output path
680 // -------------------------
682 // this distinction really looks like a
683 // micro-optimisation, but in fact greatly speeds up
684 // especially complex gradients. That's because when using
685 // clipping, we can output polygons instead of
686 // poly-polygons, and don't have to output the gradient
687 // twice for XOR
689 rOutDev.Push( PUSH_CLIPREGION );
690 rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
691 doGradientFill( rOutDev,
692 rValues,
693 rColor1,
694 rColor2,
695 aTextureTransform,
696 aPolygonDeviceRectOrig,
697 nStepCount,
698 false );
699 rOutDev.Pop();
701 if( p2ndOutDev )
703 p2ndOutDev->Push( PUSH_CLIPREGION );
704 p2ndOutDev->IntersectClipRegion( aPolygonDeviceRectOrig );
705 doGradientFill( *p2ndOutDev,
706 rValues,
707 rColor1,
708 rColor2,
709 aTextureTransform,
710 aPolygonDeviceRectOrig,
711 nStepCount,
712 false );
713 p2ndOutDev->Pop();
716 else
717 #if defined(QUARTZ) // TODO: other ports should avoid the XOR-trick too (implementation vs. interface!)
719 const Region aPolyClipRegion( rPoly );
721 rOutDev.Push( PUSH_CLIPREGION );
722 rOutDev.SetClipRegion( aPolyClipRegion );
724 doGradientFill( rOutDev,
725 rValues,
726 rColor1,
727 rColor2,
728 aTextureTransform,
729 aPolygonDeviceRectOrig,
730 nStepCount,
731 false );
732 rOutDev.Pop();
734 if( p2ndOutDev )
736 p2ndOutDev->Push( PUSH_CLIPREGION );
737 p2ndOutDev->SetClipRegion( aPolyClipRegion );
738 doGradientFill( *p2ndOutDev,
739 rValues,
740 rColor1,
741 rColor2,
742 aTextureTransform,
743 aPolygonDeviceRectOrig,
744 nStepCount,
745 false );
746 p2ndOutDev->Pop();
749 #else // TODO: remove once doing the XOR-trick in the canvas-layer becomes redundant
751 // output gradient the hard way: XORing out the polygon
752 rOutDev.Push( PUSH_RASTEROP );
753 rOutDev.SetRasterOp( ROP_XOR );
754 doGradientFill( rOutDev,
755 rValues,
756 rColor1,
757 rColor2,
758 aTextureTransform,
759 aPolygonDeviceRectOrig,
760 nStepCount,
761 true );
762 rOutDev.SetFillColor( COL_BLACK );
763 rOutDev.SetRasterOp( ROP_0 );
764 rOutDev.DrawPolyPolygon( rPoly );
765 rOutDev.SetRasterOp( ROP_XOR );
766 doGradientFill( rOutDev,
767 rValues,
768 rColor1,
769 rColor2,
770 aTextureTransform,
771 aPolygonDeviceRectOrig,
772 nStepCount,
773 true );
774 rOutDev.Pop();
776 if( p2ndOutDev )
778 p2ndOutDev->Push( PUSH_RASTEROP );
779 p2ndOutDev->SetRasterOp( ROP_XOR );
780 doGradientFill( *p2ndOutDev,
781 rValues,
782 rColor1,
783 rColor2,
784 aTextureTransform,
785 aPolygonDeviceRectOrig,
786 nStepCount,
787 true );
788 p2ndOutDev->SetFillColor( COL_BLACK );
789 p2ndOutDev->SetRasterOp( ROP_0 );
790 p2ndOutDev->DrawPolyPolygon( rPoly );
791 p2ndOutDev->SetRasterOp( ROP_XOR );
792 doGradientFill( *p2ndOutDev,
793 rValues,
794 rColor1,
795 rColor2,
796 aTextureTransform,
797 aPolygonDeviceRectOrig,
798 nStepCount,
799 true );
800 p2ndOutDev->Pop();
803 #endif // complex-clipping vs. XOR-trick
805 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
807 ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
808 ::basegfx::B2DRectangle aTextureDeviceRect;
809 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
810 aRect,
811 aTextureTransform );
812 rOutDev.SetLineColor( COL_RED );
813 rOutDev.SetFillColor();
814 rOutDev.DrawRect( ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
816 rOutDev.SetLineColor( COL_BLUE );
817 ::Polygon aPoly1(
818 ::vcl::unotools::rectangleFromB2DRectangle( aRect ));
819 ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() );
820 aPoly2.transform( aTextureTransform );
821 ::Polygon aPoly3( aPoly2 );
822 rOutDev.DrawPolygon( aPoly3 );
824 #endif
826 return true;
830 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* pCanvas,
831 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
832 const rendering::ViewState& viewState,
833 const rendering::RenderState& renderState,
834 const uno::Sequence< rendering::Texture >& textures )
836 ENSURE_ARG_OR_THROW( xPolyPolygon.is(),
837 "CanvasHelper::fillPolyPolygon(): polygon is NULL");
838 ENSURE_ARG_OR_THROW( textures.getLength(),
839 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
841 if( mpOutDev )
843 tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
845 const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) );
846 PolyPolygon aPolyPoly( tools::mapPolyPolygon(
847 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon),
848 viewState, renderState ) );
850 // TODO(F1): Multi-texturing
851 if( textures[0].Gradient.is() )
853 // try to cast XParametricPolyPolygon2D reference to
854 // our implementation class.
855 ::canvas::ParametricPolyPolygon* pGradient =
856 dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
858 if( pGradient )
860 // copy state from Gradient polypoly locally
861 // (given object might change!)
862 const ::canvas::ParametricPolyPolygon::Values& rValues(
863 pGradient->getValues() );
865 // TODO: use all the colors and place them on given positions/stops
866 const ::Color aColor1(
867 ::vcl::unotools::stdColorSpaceSequenceToColor(
868 rValues.maColors [0] ) );
869 const ::Color aColor2(
870 ::vcl::unotools::stdColorSpaceSequenceToColor(
871 rValues.maColors [rValues.maColors.getLength () - 1] ) );
873 // TODO(E1): Return value
874 // TODO(F1): FillRule
875 gradientFill( mpOutDev->getOutDev(),
876 mp2ndOutDev.get() ? &mp2ndOutDev->getOutDev() : (OutputDevice*)NULL,
877 rValues,
878 aColor1,
879 aColor2,
880 aPolyPoly,
881 viewState,
882 renderState,
883 textures[0],
884 nTransparency );
886 else
888 // TODO(F1): The generic case is missing here
889 ENSURE_OR_THROW( false,
890 "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
893 else if( textures[0].Bitmap.is() )
895 // OSL_ENSURE( textures[0].RepeatModeX == rendering::TexturingMode::REPEAT &&
896 // textures[0].RepeatModeY == rendering::TexturingMode::REPEAT,
897 // "CanvasHelper::fillTexturedPolyPolygon(): VCL canvas cannot currently clamp textures." );
899 const geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() );
901 ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
902 aBmpSize.Height != 0,
903 "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
905 // determine maximal bound rect of texture-filled
906 // polygon
907 const ::Rectangle aPolygonDeviceRect(
908 aPolyPoly.GetBoundRect() );
911 // first of all, determine whether we have a
912 // drawBitmap() in disguise
913 // =========================================
915 const bool bRectangularPolygon( tools::isRectangle( aPolyPoly ) );
917 ::basegfx::B2DHomMatrix aTotalTransform;
918 ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform,
919 viewState,
920 renderState);
921 ::basegfx::B2DHomMatrix aTextureTransform;
922 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
923 textures[0].AffineTransform );
925 aTotalTransform *= aTextureTransform;
927 const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
928 ::basegfx::B2DRectangle aTextureDeviceRect;
929 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
930 aRect,
931 aTotalTransform );
933 const ::Rectangle aIntegerTextureDeviceRect(
934 ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
936 if( bRectangularPolygon &&
937 aIntegerTextureDeviceRect == aPolygonDeviceRect )
939 rendering::RenderState aLocalState( renderState );
940 ::canvas::tools::appendToRenderState(aLocalState,
941 aTextureTransform);
942 ::basegfx::B2DHomMatrix aScaleCorrection;
943 aScaleCorrection.scale( 1.0/aBmpSize.Width,
944 1.0/aBmpSize.Height );
945 ::canvas::tools::appendToRenderState(aLocalState,
946 aScaleCorrection);
948 // need alpha modulation?
949 if( !::rtl::math::approxEqual( textures[0].Alpha,
950 1.0 ) )
952 // setup alpha modulation values
953 aLocalState.DeviceColor.realloc(4);
954 double* pColor = aLocalState.DeviceColor.getArray();
955 pColor[0] =
956 pColor[1] =
957 pColor[2] = 0.0;
958 pColor[3] = textures[0].Alpha;
960 return drawBitmapModulated( pCanvas,
961 textures[0].Bitmap,
962 viewState,
963 aLocalState );
965 else
967 return drawBitmap( pCanvas,
968 textures[0].Bitmap,
969 viewState,
970 aLocalState );
973 else if ( textures[0].RepeatModeX == rendering::TexturingMode::CLAMP &&
974 textures[0].RepeatModeY == rendering::TexturingMode::CLAMP )
976 rendering::RenderState aLocalState( renderState );
977 ::canvas::tools::appendToRenderState(aLocalState,
978 aTextureTransform);
979 ::basegfx::B2DHomMatrix aScaleCorrection;
980 aScaleCorrection.scale( 1.0/aBmpSize.Width,
981 1.0/aBmpSize.Height );
982 ::canvas::tools::appendToRenderState(aLocalState,
983 aScaleCorrection);
985 return drawBitmap( pCanvas,
986 textures[0].Bitmap,
987 viewState,
988 aLocalState );
990 else
992 // No easy mapping to drawBitmap() - calculate
993 // texturing parameters
994 // ===========================================
996 BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) );
998 // scale down bitmap to [0,1]x[0,1] rect, as required
999 // from the XCanvas interface.
1000 ::basegfx::B2DHomMatrix aScaling;
1001 ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform
1002 aScaling.scale( 1.0/aBmpSize.Width,
1003 1.0/aBmpSize.Height );
1005 aTotalTransform = aTextureTransform * aScaling;
1006 aPureTotalTransform = aTextureTransform;
1008 // combine with view and render transform
1009 ::basegfx::B2DHomMatrix aMatrix;
1010 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);
1012 // combine all three transformations into one
1013 // global texture-to-device-space transformation
1014 aTotalTransform *= aMatrix;
1015 aPureTotalTransform *= aMatrix;
1017 // analyze transformation, and setup an
1018 // appropriate GraphicObject
1019 ::basegfx::B2DVector aScale;
1020 ::basegfx::B2DPoint aOutputPos;
1021 double nRotate;
1022 double nShearX;
1023 aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX );
1025 GraphicAttr aGrfAttr;
1026 GraphicObjectSharedPtr pGrfObj;
1028 if( ::basegfx::fTools::equalZero( nShearX ) )
1030 // no shear, GraphicObject is enough (the
1031 // GraphicObject only supports scaling, rotation
1032 // and translation)
1034 // setup GraphicAttr
1035 aGrfAttr.SetMirrorFlags(
1036 ( aScale.getX() < 0.0 ? BMP_MIRROR_HORZ : 0 ) |
1037 ( aScale.getY() < 0.0 ? BMP_MIRROR_VERT : 0 ) );
1038 aGrfAttr.SetRotation( static_cast< USHORT >(::basegfx::fround( nRotate*10.0 )) );
1040 pGrfObj.reset( new GraphicObject( aBmpEx ) );
1042 else
1044 // complex transformation, use generic affine bitmap
1045 // transformation
1046 aBmpEx = tools::transformBitmap( aBmpEx,
1047 aTotalTransform,
1048 uno::Sequence< double >(),
1049 tools::MODULATE_NONE);
1051 pGrfObj.reset( new GraphicObject( aBmpEx ) );
1053 // clear scale values, generated bitmap already
1054 // contains scaling
1055 aScale.setX( 0.0 ); aScale.setY( 0.0 );
1059 // render texture tiled into polygon
1060 // =================================
1062 // calc device space direction vectors. We employ
1063 // the followin approach for tiled output: the
1064 // texture bitmap is output in texture space
1065 // x-major order, i.e. tile neighbors in texture
1066 // space x direction are rendered back-to-back in
1067 // device coordinate space (after the full device
1068 // transformation). Thus, the aNextTile* vectors
1069 // denote the output position updates in device
1070 // space, to get from one tile to the next.
1071 ::basegfx::B2DVector aNextTileX( 1.0, 0.0 );
1072 ::basegfx::B2DVector aNextTileY( 0.0, 1.0 );
1073 aNextTileX *= aPureTotalTransform;
1074 aNextTileY *= aPureTotalTransform;
1076 ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform );
1078 ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(),
1079 "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
1081 aInverseTextureTransform.invert();
1083 // calc bound rect of extended texture area in
1084 // device coordinates. Therefore, we first calc
1085 // the area of the polygon bound rect in texture
1086 // space. To maintain texture phase, this bound
1087 // rect is then extended to integer coordinates
1088 // (extended, because shrinking might leave some
1089 // inner polygon areas unfilled).
1090 // Finally, the bound rect is transformed back to
1091 // device coordinate space, were we determine the
1092 // start point from it.
1093 ::basegfx::B2DRectangle aTextureSpacePolygonRect;
1094 ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect,
1095 ::vcl::unotools::b2DRectangleFromRectangle(
1096 aPolygonDeviceRect ),
1097 aInverseTextureTransform );
1099 // calc left, top of extended polygon rect in
1100 // texture space, create one-texture instance rect
1101 // from it (i.e. rect from start point extending
1102 // 1.0 units to the right and 1.0 units to the
1103 // bottom). Note that the rounding employed here
1104 // is a bit subtle, since we need to round up/down
1105 // as _soon_ as any fractional amount is
1106 // encountered. This is to ensure that the full
1107 // polygon area is filled with texture tiles.
1108 const sal_Int32 nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) );
1109 const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) );
1110 const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) );
1111 const sal_Int32 nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxY() ) );
1112 const ::basegfx::B2DRectangle aSingleTextureRect(
1113 nX1, nY1,
1114 nX1 + 1.0,
1115 nY1 + 1.0 );
1117 // and convert back to device space
1118 ::basegfx::B2DRectangle aSingleDeviceTextureRect;
1119 ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect,
1120 aSingleTextureRect,
1121 aPureTotalTransform );
1123 const ::Point aPt( ::vcl::unotools::pointFromB2DPoint(
1124 aSingleDeviceTextureRect.getMinimum() ) );
1125 const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ),
1126 ::basegfx::fround( aScale.getY() * aBmpSize.Height ) );
1127 const ::Size aIntegerNextTileX( ::vcl::unotools::sizeFromB2DSize(aNextTileX) );
1128 const ::Size aIntegerNextTileY( ::vcl::unotools::sizeFromB2DSize(aNextTileY) );
1130 const sal_Int32 nTilesX( nX2 - nX1 );
1131 const sal_Int32 nTilesY( nY2 - nY1 );
1133 OutputDevice& rOutDev( mpOutDev->getOutDev() );
1135 if( bRectangularPolygon )
1137 // use optimized output path
1138 // -------------------------
1140 // this distinction really looks like a
1141 // micro-optimisation, but in fact greatly speeds up
1142 // especially complex fills. That's because when using
1143 // clipping, we can output polygons instead of
1144 // poly-polygons, and don't have to output the gradient
1145 // twice for XOR
1147 // setup alpha modulation
1148 if( !::rtl::math::approxEqual( textures[0].Alpha,
1149 1.0 ) )
1151 // TODO(F1): Note that the GraphicManager has
1152 // a subtle difference in how it calculates
1153 // the resulting alpha value: it's using the
1154 // inverse alpha values (i.e. 'transparency'),
1155 // and calculates transOrig + transModulate,
1156 // instead of transOrig + transModulate -
1157 // transOrig*transModulate (which would be
1158 // equivalent to the origAlpha*modulateAlpha
1159 // the DX canvas performs)
1160 aGrfAttr.SetTransparency(
1161 static_cast< BYTE >(
1162 ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1165 rOutDev.IntersectClipRegion( aPolygonDeviceRect );
1166 textureFill( rOutDev,
1167 *pGrfObj,
1168 aPt,
1169 aIntegerNextTileX,
1170 aIntegerNextTileY,
1171 nTilesX,
1172 nTilesY,
1173 aSz,
1174 aGrfAttr );
1176 if( mp2ndOutDev )
1178 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1179 r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
1180 textureFill( r2ndOutDev,
1181 *pGrfObj,
1182 aPt,
1183 aIntegerNextTileX,
1184 aIntegerNextTileY,
1185 nTilesX,
1186 nTilesY,
1187 aSz,
1188 aGrfAttr );
1191 else
1193 // output texture the hard way: XORing out the
1194 // polygon
1195 // ===========================================
1197 if( !::rtl::math::approxEqual( textures[0].Alpha,
1198 1.0 ) )
1200 // uh-oh. alpha blending is required,
1201 // cannot do direct XOR, but have to
1202 // prepare the filled polygon within a
1203 // VDev
1204 VirtualDevice aVDev( rOutDev );
1205 aVDev.SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
1207 // shift output to origin of VDev
1208 const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
1209 aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
1210 -aPolygonDeviceRect.Top() ) );
1212 aVDev.SetRasterOp( ROP_XOR );
1213 textureFill( aVDev,
1214 *pGrfObj,
1215 aOutPos,
1216 aIntegerNextTileX,
1217 aIntegerNextTileY,
1218 nTilesX,
1219 nTilesY,
1220 aSz,
1221 aGrfAttr );
1222 aVDev.SetFillColor( COL_BLACK );
1223 aVDev.SetRasterOp( ROP_0 );
1224 aVDev.DrawPolyPolygon( aPolyPoly );
1225 aVDev.SetRasterOp( ROP_XOR );
1226 textureFill( aVDev,
1227 *pGrfObj,
1228 aOutPos,
1229 aIntegerNextTileX,
1230 aIntegerNextTileY,
1231 nTilesX,
1232 nTilesY,
1233 aSz,
1234 aGrfAttr );
1236 // output VDev content alpha-blended to
1237 // target position.
1238 const ::Point aEmptyPoint;
1239 Bitmap aContentBmp(
1240 aVDev.GetBitmap( aEmptyPoint,
1241 aVDev.GetOutputSizePixel() ) );
1243 BYTE nCol( static_cast< BYTE >(
1244 ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1245 AlphaMask aAlpha( aVDev.GetOutputSizePixel(),
1246 &nCol );
1248 BitmapEx aOutputBmpEx( aContentBmp, aAlpha );
1249 rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1250 aOutputBmpEx );
1252 if( mp2ndOutDev )
1253 mp2ndOutDev->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1254 aOutputBmpEx );
1256 else
1257 #if defined(QUARTZ) // TODO: other ports should avoid the XOR-trick too (implementation vs. interface!)
1259 const Region aPolyClipRegion( aPolyPoly );
1261 rOutDev.Push( PUSH_CLIPREGION );
1262 rOutDev.SetClipRegion( aPolyClipRegion );
1264 textureFill( rOutDev,
1265 *pGrfObj,
1266 aPt,
1267 aIntegerNextTileX,
1268 aIntegerNextTileY,
1269 nTilesX,
1270 nTilesY,
1271 aSz,
1272 aGrfAttr );
1273 rOutDev.Pop();
1275 if( mp2ndOutDev )
1277 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1278 r2ndOutDev.Push( PUSH_CLIPREGION );
1280 r2ndOutDev.SetClipRegion( aPolyClipRegion );
1281 textureFill( r2ndOutDev,
1282 *pGrfObj,
1283 aPt,
1284 aIntegerNextTileX,
1285 aIntegerNextTileY,
1286 nTilesX,
1287 nTilesY,
1288 aSz,
1289 aGrfAttr );
1290 r2ndOutDev.Pop();
1293 #else // TODO: remove once doing the XOR-trick in the canvas-layer becomes redundant
1295 // output via repeated XORing
1296 rOutDev.Push( PUSH_RASTEROP );
1297 rOutDev.SetRasterOp( ROP_XOR );
1298 textureFill( rOutDev,
1299 *pGrfObj,
1300 aPt,
1301 aIntegerNextTileX,
1302 aIntegerNextTileY,
1303 nTilesX,
1304 nTilesY,
1305 aSz,
1306 aGrfAttr );
1307 rOutDev.SetFillColor( COL_BLACK );
1308 rOutDev.SetRasterOp( ROP_0 );
1309 rOutDev.DrawPolyPolygon( aPolyPoly );
1310 rOutDev.SetRasterOp( ROP_XOR );
1311 textureFill( rOutDev,
1312 *pGrfObj,
1313 aPt,
1314 aIntegerNextTileX,
1315 aIntegerNextTileY,
1316 nTilesX,
1317 nTilesY,
1318 aSz,
1319 aGrfAttr );
1320 rOutDev.Pop();
1322 if( mp2ndOutDev )
1324 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1325 r2ndOutDev.Push( PUSH_RASTEROP );
1326 r2ndOutDev.SetRasterOp( ROP_XOR );
1327 textureFill( r2ndOutDev,
1328 *pGrfObj,
1329 aPt,
1330 aIntegerNextTileX,
1331 aIntegerNextTileY,
1332 nTilesX,
1333 nTilesY,
1334 aSz,
1335 aGrfAttr );
1336 r2ndOutDev.SetFillColor( COL_BLACK );
1337 r2ndOutDev.SetRasterOp( ROP_0 );
1338 r2ndOutDev.DrawPolyPolygon( aPolyPoly );
1339 r2ndOutDev.SetRasterOp( ROP_XOR );
1340 textureFill( r2ndOutDev,
1341 *pGrfObj,
1342 aPt,
1343 aIntegerNextTileX,
1344 aIntegerNextTileY,
1345 nTilesX,
1346 nTilesY,
1347 aSz,
1348 aGrfAttr );
1349 r2ndOutDev.Pop();
1352 #endif // complex-clipping vs. XOR-trick
1358 // TODO(P1): Provide caching here.
1359 return uno::Reference< rendering::XCachedPrimitive >(NULL);