1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: canvashelper_texturefill.cxx,v $
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
;
79 bool textureFill( OutputDevice
& rOutDev
,
80 GraphicObject
& rGraphic
,
81 const ::Point
& rPosPixel
,
82 const ::Size
& rNextTileX
,
83 const ::Size
& rNextTileY
,
86 const ::Size
& rTileSize
,
87 const GraphicAttr
& rAttr
)
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
,
107 aCurrPos
.X() += rNextTileX
.Width();
108 aCurrPos
.Y() += rNextTileX
.Height();
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
,
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
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:
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
,
168 ::vcl::unotools::b2DRectangleFromRectangle( rBounds
) );
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) )
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
)
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
) ) );
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
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() ) );
287 rOutDev
.SetFillColor( rColor1
);
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
,
301 bool bFillNonOverlapping
)
303 fillGeneralLinearGradient( rOutDev
,
313 inline void fillAxialGradient( OutputDevice
& rOutDev
,
314 const ::Color
& rColor1
,
315 const ::Color
& rColor2
,
316 const ::basegfx::B2DHomMatrix
& rTextureTransform
,
317 const ::Rectangle
& rBounds
,
319 bool bFillNonOverlapping
)
321 fillGeneralLinearGradient( rOutDev
,
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
,
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
,
387 else if( nAspectRatio
< 1.0 )
389 // width < height case
390 aInnerPolygonTransformMatrix
.scale( 0.0,
391 1.0 - nAspectRatio
);
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:
419 // |-------|-------|-------|
423 // This yields 4 tick marks, where 0 is never attained (since
424 // zero-area polygons typically don't display perceivable
428 if( !bFillNonOverlapping
)
431 rOutDev
.SetFillColor( rColor1
);
432 rOutDev
.DrawRect( rBounds
);
437 for( int i
=1,p
; i
<nStepCount
; ++i
)
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
468 rOutDev
.DrawPolygon( aTempPoly
);
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
)
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
502 rOutDev
.SetFillColor( COL_RED
);
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 );
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 );
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
,
557 bool bFillNonOverlapping
)
559 switch( rValues
.meType
)
561 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR
:
562 fillLinearGradient( rOutDev
,
568 bFillNonOverlapping
);
571 case ::canvas::ParametricPolyPolygon::GRADIENT_AXIAL
:
572 fillAxialGradient( rOutDev
,
578 bFillNonOverlapping
);
581 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL
:
582 // FALLTHROUGH intended
583 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR
:
584 fillPolygonalGradient( rOutDev
,
591 bFillNonOverlapping
);
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
,
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
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
,
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
;
649 const int nColorSteps(
651 labs( rColor1
.GetRed() - rColor2
.GetRed() ),
653 labs( rColor1
.GetGreen() - rColor2
.GetGreen() ),
654 labs( rColor1
.GetBlue() - rColor2
.GetBlue() ) ) ) );
656 // longest line in gradient bound rect
657 const int nGradientSize(
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
668 const int nStepCount(
672 nGradientSize
/ nStripSize
,
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
689 rOutDev
.Push( PUSH_CLIPREGION
);
690 rOutDev
.IntersectClipRegion( aPolygonDeviceRectOrig
);
691 doGradientFill( rOutDev
,
696 aPolygonDeviceRectOrig
,
703 p2ndOutDev
->Push( PUSH_CLIPREGION
);
704 p2ndOutDev
->IntersectClipRegion( aPolygonDeviceRectOrig
);
705 doGradientFill( *p2ndOutDev
,
710 aPolygonDeviceRectOrig
,
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
,
729 aPolygonDeviceRectOrig
,
736 p2ndOutDev
->Push( PUSH_CLIPREGION
);
737 p2ndOutDev
->SetClipRegion( aPolyClipRegion
);
738 doGradientFill( *p2ndOutDev
,
743 aPolygonDeviceRectOrig
,
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
,
759 aPolygonDeviceRectOrig
,
762 rOutDev
.SetFillColor( COL_BLACK
);
763 rOutDev
.SetRasterOp( ROP_0
);
764 rOutDev
.DrawPolyPolygon( rPoly
);
765 rOutDev
.SetRasterOp( ROP_XOR
);
766 doGradientFill( rOutDev
,
771 aPolygonDeviceRectOrig
,
778 p2ndOutDev
->Push( PUSH_RASTEROP
);
779 p2ndOutDev
->SetRasterOp( ROP_XOR
);
780 doGradientFill( *p2ndOutDev
,
785 aPolygonDeviceRectOrig
,
788 p2ndOutDev
->SetFillColor( COL_BLACK
);
789 p2ndOutDev
->SetRasterOp( ROP_0
);
790 p2ndOutDev
->DrawPolyPolygon( rPoly
);
791 p2ndOutDev
->SetRasterOp( ROP_XOR
);
792 doGradientFill( *p2ndOutDev
,
797 aPolygonDeviceRectOrig
,
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
,
812 rOutDev
.SetLineColor( COL_RED
);
813 rOutDev
.SetFillColor();
814 rOutDev
.DrawRect( ::vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect
) );
816 rOutDev
.SetLineColor( COL_BLUE
);
818 ::vcl::unotools::rectangleFromB2DRectangle( aRect
));
819 ::basegfx::B2DPolygon
aPoly2( aPoly1
.getB2DPolygon() );
820 aPoly2
.transform( aTextureTransform
);
821 ::Polygon
aPoly3( aPoly2
);
822 rOutDev
.DrawPolygon( aPoly3
);
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");
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() );
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
,
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
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
,
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
,
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
,
942 ::basegfx::B2DHomMatrix aScaleCorrection
;
943 aScaleCorrection
.scale( 1.0/aBmpSize
.Width
,
944 1.0/aBmpSize
.Height
);
945 ::canvas::tools::appendToRenderState(aLocalState
,
948 // need alpha modulation?
949 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
952 // setup alpha modulation values
953 aLocalState
.DeviceColor
.realloc(4);
954 double* pColor
= aLocalState
.DeviceColor
.getArray();
958 pColor
[3] = textures
[0].Alpha
;
960 return drawBitmapModulated( pCanvas
,
967 return drawBitmap( pCanvas
,
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
,
979 ::basegfx::B2DHomMatrix aScaleCorrection
;
980 aScaleCorrection
.scale( 1.0/aBmpSize
.Width
,
981 1.0/aBmpSize
.Height
);
982 ::canvas::tools::appendToRenderState(aLocalState
,
985 return drawBitmap( pCanvas
,
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
;
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
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
) );
1044 // complex transformation, use generic affine bitmap
1046 aBmpEx
= tools::transformBitmap( aBmpEx
,
1048 uno::Sequence
< double >(),
1049 tools::MODULATE_NONE
);
1051 pGrfObj
.reset( new GraphicObject( aBmpEx
) );
1053 // clear scale values, generated bitmap already
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(
1117 // and convert back to device space
1118 ::basegfx::B2DRectangle aSingleDeviceTextureRect
;
1119 ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect
,
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
1147 // setup alpha modulation
1148 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
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
,
1178 OutputDevice
& r2ndOutDev( mp2ndOutDev
->getOutDev() );
1179 r2ndOutDev
.IntersectClipRegion( aPolygonDeviceRect
);
1180 textureFill( r2ndOutDev
,
1193 // output texture the hard way: XORing out the
1195 // ===========================================
1197 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
1200 // uh-oh. alpha blending is required,
1201 // cannot do direct XOR, but have to
1202 // prepare the filled polygon within a
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
);
1222 aVDev
.SetFillColor( COL_BLACK
);
1223 aVDev
.SetRasterOp( ROP_0
);
1224 aVDev
.DrawPolyPolygon( aPolyPoly
);
1225 aVDev
.SetRasterOp( ROP_XOR
);
1236 // output VDev content alpha-blended to
1238 const ::Point aEmptyPoint
;
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(),
1248 BitmapEx
aOutputBmpEx( aContentBmp
, aAlpha
);
1249 rOutDev
.DrawBitmapEx( aPolygonDeviceRect
.TopLeft(),
1253 mp2ndOutDev
->getOutDev().DrawBitmapEx( aPolygonDeviceRect
.TopLeft(),
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
,
1277 OutputDevice
& r2ndOutDev( mp2ndOutDev
->getOutDev() );
1278 r2ndOutDev
.Push( PUSH_CLIPREGION
);
1280 r2ndOutDev
.SetClipRegion( aPolyClipRegion
);
1281 textureFill( r2ndOutDev
,
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
,
1307 rOutDev
.SetFillColor( COL_BLACK
);
1308 rOutDev
.SetRasterOp( ROP_0
);
1309 rOutDev
.DrawPolyPolygon( aPolyPoly
);
1310 rOutDev
.SetRasterOp( ROP_XOR
);
1311 textureFill( rOutDev
,
1324 OutputDevice
& r2ndOutDev( mp2ndOutDev
->getOutDev() );
1325 r2ndOutDev
.Push( PUSH_RASTEROP
);
1326 r2ndOutDev
.SetRasterOp( ROP_XOR
);
1327 textureFill( r2ndOutDev
,
1336 r2ndOutDev
.SetFillColor( COL_BLACK
);
1337 r2ndOutDev
.SetRasterOp( ROP_0
);
1338 r2ndOutDev
.DrawPolyPolygon( aPolyPoly
);
1339 r2ndOutDev
.SetRasterOp( ROP_XOR
);
1340 textureFill( r2ndOutDev
,
1352 #endif // complex-clipping vs. XOR-trick
1358 // TODO(P1): Provide caching here.
1359 return uno::Reference
< rendering::XCachedPrimitive
>(NULL
);