1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
25 #include <basegfx/matrix/b2dhommatrix.hxx>
26 #include <basegfx/numeric/ftools.hxx>
27 #include <basegfx/point/b2dpoint.hxx>
28 #include <basegfx/polygon/b2dpolygon.hxx>
29 #include <basegfx/polygon/b2dpolygontools.hxx>
30 #include <basegfx/range/b2drectangle.hxx>
31 #include <basegfx/utils/canvastools.hxx>
32 #include <basegfx/utils/keystoplerp.hxx>
33 #include <basegfx/utils/lerp.hxx>
34 #include <basegfx/utils/tools.hxx>
35 #include <com/sun/star/rendering/TexturingMode.hpp>
36 #include <rtl/math.hxx>
37 #include <comphelper/diagnose_ex.hxx>
38 #include <tools/poly.hxx>
39 #include <vcl/bitmapex.hxx>
40 #include <vcl/canvastools.hxx>
41 #include <vcl/virdev.hxx>
42 #include <vcl/gradient.hxx>
44 #include <canvas/canvastools.hxx>
45 #include <parametricpolypolygon.hxx>
47 #include "canvashelper.hxx"
48 #include "impltools.hxx"
51 using namespace ::com::sun::star
;
57 bool textureFill( OutputDevice
& rOutDev
,
58 const GraphicObject
& rGraphic
,
59 const ::Point
& rPosPixel
,
60 const ::Size
& rNextTileX
,
61 const ::Size
& rNextTileY
,
64 const ::Size
& rTileSize
,
65 const GraphicAttr
& rAttr
)
71 for( nY
=0; nY
< nTilesY
; ++nY
)
73 aCurrPos
.setX( rPosPixel
.X() + nY
*rNextTileY
.Width() );
74 aCurrPos
.setY( rPosPixel
.Y() + nY
*rNextTileY
.Height() );
76 for( nX
=0; nX
< nTilesX
; ++nX
)
78 // update return value. This method should return true, if
79 // at least one of the looped Draws succeeded.
80 bRet
|= rGraphic
.Draw(rOutDev
,
85 aCurrPos
.AdjustX(rNextTileX
.Width() );
86 aCurrPos
.AdjustY(rNextTileX
.Height() );
94 /** Fill linear or axial gradient
96 Since most of the code for linear and axial gradients are
97 the same, we've a unified method here
99 void fillLinearGradient( OutputDevice
& rOutDev
,
100 const ::basegfx::B2DHomMatrix
& rTextureTransform
,
101 const ::tools::Rectangle
& rBounds
,
102 unsigned int nStepCount
,
103 const ::canvas::ParametricPolyPolygon::Values
& rValues
,
104 const std::vector
< ::Color
>& rColors
)
106 // determine general position of gradient in relation to
108 // =====================================================
110 ::basegfx::B2DPoint
aLeftTop( 0.0, 0.0 );
111 ::basegfx::B2DPoint
aLeftBottom( 0.0, 1.0 );
112 ::basegfx::B2DPoint
aRightTop( 1.0, 0.0 );
113 ::basegfx::B2DPoint
aRightBottom( 1.0, 1.0 );
115 aLeftTop
*= rTextureTransform
;
116 aLeftBottom
*= rTextureTransform
;
117 aRightTop
*= rTextureTransform
;
118 aRightBottom
*= rTextureTransform
;
120 // calc length of bound rect diagonal
121 const ::basegfx::B2DVector
aBoundRectDiagonal(
122 vcl::unotools::b2DPointFromPoint( rBounds
.TopLeft() ) -
123 vcl::unotools::b2DPointFromPoint( rBounds
.BottomRight() ) );
124 const double nDiagonalLength( aBoundRectDiagonal
.getLength() );
126 // create direction of gradient:
132 ::basegfx::B2DVector
aDirection( aRightTop
- aLeftTop
);
133 aDirection
.normalize();
135 // now, we potentially have to enlarge our gradient area
136 // atop and below the transformed [0,1]x[0,1] unit rect,
137 // for the gradient to fill the complete bound rect.
138 ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop
,
142 vcl::unotools::b2DRectangleFromRectangle(rBounds
) );
148 // First try to use directly VCL's DrawGradient(), as that one is generally
149 // a better choice than here decomposing to polygons. The VCL API allows
150 // only 2 colors, but that should generally do.
151 // Do not use nStepCount, it limits optimized implementations, and it's computed
152 // by vclcanvas based on number of colors, so it's practically irrelevant.
154 // 2 colors and 2 stops (at 0 and 1) is a linear gradient:
155 if( rColors
.size() == 2 && rValues
.maStops
.size() == 2 && rValues
.maStops
[0] == 0 && rValues
.maStops
[1] == 1)
157 // tdf#144073 and tdf#147645: use bounds and angle for gradient
158 // Passing an expanded, rotated polygon noticeably modifies the
159 // drawing of the gradient in a slideshow due to moving of the
160 // starting and ending colors far off the edges of the drawing
161 // surface. So try another way and set the angle of the
162 // gradient and draw only the unadjusted bounds.
163 Gradient
vclGradient( css::awt::GradientStyle_LINEAR
, rColors
[ 0 ], rColors
[ 1 ] );
164 double fRotate
= atan2( aDirection
.getY(), aDirection
.getX() );
165 const double nAngleInTenthOfDegrees
= 3600.0 - basegfx::rad2deg
<10>( fRotate
) + 900.0;
166 vclGradient
.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees
) ) );
167 rOutDev
.DrawGradient( rBounds
, vclGradient
);
170 // 3 colors with first and last being equal and 3 stops (at 0, 0.5 and 1) is an axial gradient:
171 if( rColors
.size() == 3 && rColors
[ 0 ] == rColors
[ 2 ]
172 && rValues
.maStops
.size() == 3 && rValues
.maStops
[0] == 0
173 && rValues
.maStops
[1] == 0.5 && rValues
.maStops
[2] == 1)
175 // tdf#144073 and tdf#147645: use bounds and angle for gradient
176 // Passing an expanded, rotated polygon noticeably modifies the
177 // drawing of the gradient in a slideshow due to moving of the
178 // starting and ending colors far off the edges of the drawing
179 // surface. So try another way and set the angle of the
180 // gradient and draw only the unadjusted bounds.
181 Gradient
vclGradient( css::awt::GradientStyle_AXIAL
, rColors
[ 1 ], rColors
[ 0 ] );
182 double fRotate
= atan2( aDirection
.getY(), aDirection
.getX() );
183 const double nAngleInTenthOfDegrees
= 3600.0 - basegfx::rad2deg
<10>( fRotate
) + 900.0;
184 vclGradient
.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees
) ) );
185 rOutDev
.DrawGradient( rBounds
, vclGradient
);
189 // for linear gradients, it's easy to render
190 // non-overlapping polygons: just split the gradient into
191 // nStepCount small strips. Prepare the strip now.
193 // For performance reasons, we create a temporary VCL
194 // polygon here, keep it all the way and only change the
195 // vertex values in the loop below (as ::Polygon is a
196 // pimpl class, creating one every loop turn would really
197 // stress the mem allocator)
198 ::tools::Polygon
aTempPoly( static_cast<sal_uInt16
>(5) );
200 OSL_ENSURE( nStepCount
>= 3,
201 "fillLinearGradient(): stepcount smaller than 3" );
204 // fill initial strip (extending two times the bound rect's
205 // diagonal to the 'left'
208 // calculate left edge, by moving left edge of the
209 // gradient rect two times the bound rect's diagonal to
210 // the 'left'. Since we postpone actual rendering into the
211 // loop below, we set the _right_ edge here, which will be
212 // readily copied into the left edge in the loop below
213 const ::basegfx::B2DPoint
aPoint1( aLeftTop
- 2.0*nDiagonalLength
*aDirection
);
214 aTempPoly
[1] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint1
.getX() ),
215 ::basegfx::fround
<::tools::Long
>( aPoint1
.getY() ) );
217 const ::basegfx::B2DPoint
aPoint2( aLeftBottom
- 2.0*nDiagonalLength
*aDirection
);
218 aTempPoly
[2] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint2
.getX() ),
219 ::basegfx::fround
<::tools::Long
>( aPoint2
.getY() ) );
222 // iteratively render all other strips
225 // ensure that nStepCount matches color stop parity, to
226 // have a well-defined middle color e.g. for axial
228 if( (rColors
.size() % 2) != (nStepCount
% 2) )
231 rOutDev
.SetLineColor();
233 basegfx::utils::KeyStopLerp
aLerper(rValues
.maStops
);
235 // only iterate nStepCount-1 steps, as the last strip is
236 // explicitly painted below
237 for( unsigned int i
=0; i
<nStepCount
-1; ++i
)
239 std::ptrdiff_t nIndex
;
241 std::tie(nIndex
,fAlpha
)=aLerper
.lerp(double(i
)/nStepCount
);
243 rOutDev
.SetFillColor(
244 Color( static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetRed(),rColors
[nIndex
+1].GetRed(),fAlpha
)),
245 static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetGreen(),rColors
[nIndex
+1].GetGreen(),fAlpha
)),
246 static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetBlue(),rColors
[nIndex
+1].GetBlue(),fAlpha
)) ));
248 // copy right edge of polygon to left edge (and also
249 // copy the closing point)
250 aTempPoly
[0] = aTempPoly
[4] = aTempPoly
[1];
251 aTempPoly
[3] = aTempPoly
[2];
253 // calculate new right edge, from interpolating
254 // between start and end line. Note that i is
255 // increased by one, to account for the fact that we
256 // calculate the right border here (whereas the fill
257 // color is governed by the left edge)
258 const ::basegfx::B2DPoint
aPoint3(
259 (nStepCount
- i
-1)/double(nStepCount
)*aLeftTop
+
260 (i
+1)/double(nStepCount
)*aRightTop
);
261 aTempPoly
[1] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint3
.getX() ),
262 ::basegfx::fround
<::tools::Long
>( aPoint3
.getY() ) );
264 const ::basegfx::B2DPoint
aPoint4(
265 (nStepCount
- i
-1)/double(nStepCount
)*aLeftBottom
+
266 (i
+1)/double(nStepCount
)*aRightBottom
);
267 aTempPoly
[2] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint4
.getX() ),
268 ::basegfx::fround
<::tools::Long
>( aPoint4
.getY() ) );
270 rOutDev
.DrawPolygon( aTempPoly
);
273 // fill final strip (extending two times the bound rect's
274 // diagonal to the 'right'
277 // copy right edge of polygon to left edge (and also
278 // copy the closing point)
279 aTempPoly
[0] = aTempPoly
[4] = aTempPoly
[1];
280 aTempPoly
[3] = aTempPoly
[2];
282 // calculate new right edge, by moving right edge of the
283 // gradient rect two times the bound rect's diagonal to
285 const ::basegfx::B2DPoint
aPoint3( aRightTop
+ 2.0*nDiagonalLength
*aDirection
);
286 aTempPoly
[0] = aTempPoly
[4] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint3
.getX() ),
287 ::basegfx::fround
<::tools::Long
>( aPoint3
.getY() ) );
289 const ::basegfx::B2DPoint
aPoint4( aRightBottom
+ 2.0*nDiagonalLength
*aDirection
);
290 aTempPoly
[3] = ::Point( ::basegfx::fround
<::tools::Long
>( aPoint4
.getX() ),
291 ::basegfx::fround
<::tools::Long
>( aPoint4
.getY() ) );
293 rOutDev
.SetFillColor( rColors
.back() );
295 rOutDev
.DrawPolygon( aTempPoly
);
298 void fillPolygonalGradient( OutputDevice
& rOutDev
,
299 const ::basegfx::B2DHomMatrix
& rTextureTransform
,
300 const ::tools::Rectangle
& rBounds
,
301 unsigned int nStepCount
,
302 const ::canvas::ParametricPolyPolygon::Values
& rValues
,
303 const std::vector
< ::Color
>& rColors
)
305 const ::basegfx::B2DPolygon
& rGradientPoly( rValues
.maGradientPoly
);
307 ENSURE_OR_THROW( rGradientPoly
.count() > 2,
308 "fillPolygonalGradient(): polygon without area given" );
310 // For performance reasons, we create a temporary VCL polygon
311 // here, keep it all the way and only change the vertex values
312 // in the loop below (as ::Polygon is a pimpl class, creating
313 // one every loop turn would really stress the mem allocator)
314 ::basegfx::B2DPolygon
aOuterPoly( rGradientPoly
);
315 ::basegfx::B2DPolygon aInnerPoly
;
317 // subdivide polygon _before_ rendering, would otherwise have
318 // to be performed on every loop turn.
319 if( aOuterPoly
.areControlPointsUsed() )
320 aOuterPoly
= ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly
);
322 aInnerPoly
= aOuterPoly
;
324 // only transform outer polygon _after_ copying it into
325 // aInnerPoly, because inner polygon has to be scaled before
326 // the actual texture transformation takes place
327 aOuterPoly
.transform( rTextureTransform
);
329 // determine overall transformation for inner polygon (might
330 // have to be prefixed by anisotrophic scaling)
331 ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix
;
334 // apply scaling (possibly anisotrophic) to inner polygon
337 // scale inner polygon according to aspect ratio: for
338 // wider-than-tall bounds (nAspectRatio > 1.0), the inner
339 // polygon, representing the gradient focus, must have
340 // non-zero width. Specifically, a bound rect twice as wide as
341 // tall has a focus polygon of half its width.
342 const double nAspectRatio( rValues
.mnAspectRatio
);
343 if( nAspectRatio
> 1.0 )
345 // width > height case
346 aInnerPolygonTransformMatrix
.scale( 1.0 - 1.0/nAspectRatio
,
349 else if( nAspectRatio
< 1.0 )
351 // width < height case
352 aInnerPolygonTransformMatrix
.scale( 0.0,
353 1.0 - nAspectRatio
);
358 aInnerPolygonTransformMatrix
.scale( 0.0, 0.0 );
361 // and finally, add texture transform to it.
362 aInnerPolygonTransformMatrix
*= rTextureTransform
;
364 // apply final matrix to polygon
365 aInnerPoly
.transform( aInnerPolygonTransformMatrix
);
368 const sal_uInt32
nNumPoints( aOuterPoly
.count() );
369 ::tools::Polygon
aTempPoly( static_cast<sal_uInt16
>(nNumPoints
+1) );
371 // increase number of steps by one: polygonal gradients have
372 // the outermost polygon rendered in rColor2, and the
373 // innermost in rColor1. The innermost polygon will never
374 // have zero area, thus, we must divide the interval into
375 // nStepCount+1 steps. For example, to create 3 steps:
378 // |-------|-------|-------|
382 // This yields 4 tick marks, where 0 is never attained (since
383 // zero-area polygons typically don't display perceivable
387 rOutDev
.SetLineColor();
389 basegfx::utils::KeyStopLerp
aLerper(rValues
.maStops
);
392 rOutDev
.SetFillColor( rColors
.front() );
393 rOutDev
.DrawRect( rBounds
);
398 for( unsigned int i
=1,p
; i
<nStepCount
; ++i
)
400 const double fT( i
/double(nStepCount
) );
402 std::ptrdiff_t nIndex
;
404 std::tie(nIndex
,fAlpha
)=aLerper
.lerp(fT
);
407 rOutDev
.SetFillColor(
408 Color( static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetRed(),rColors
[nIndex
+1].GetRed(),fAlpha
)),
409 static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetGreen(),rColors
[nIndex
+1].GetGreen(),fAlpha
)),
410 static_cast<sal_uInt8
>(basegfx::utils::lerp(rColors
[nIndex
].GetBlue(),rColors
[nIndex
+1].GetBlue(),fAlpha
)) ));
412 // scale and render polygon, by interpolating between
413 // outer and inner polygon.
415 for( p
=0; p
<nNumPoints
; ++p
)
417 const ::basegfx::B2DPoint
& rOuterPoint( aOuterPoly
.getB2DPoint(p
) );
418 const ::basegfx::B2DPoint
& rInnerPoint( aInnerPoly
.getB2DPoint(p
) );
420 aTempPoly
[static_cast<sal_uInt16
>(p
)] = ::Point(
421 basegfx::fround
<::tools::Long
>( fT
*rInnerPoint
.getX() + (1-fT
)*rOuterPoint
.getX() ),
422 basegfx::fround
<::tools::Long
>( fT
*rInnerPoint
.getY() + (1-fT
)*rOuterPoint
.getY() ) );
425 // close polygon explicitly
426 aTempPoly
[static_cast<sal_uInt16
>(p
)] = aTempPoly
[0];
428 // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
429 // OutputDevice::ImplDrawComplexGradient(), there's a note
430 // that on some VDev's, rendering disjunct poly-polygons
432 rOutDev
.DrawPolygon( aTempPoly
);
436 void doGradientFill( OutputDevice
& rOutDev
,
437 const ::canvas::ParametricPolyPolygon::Values
& rValues
,
438 const std::vector
< ::Color
>& rColors
,
439 const ::basegfx::B2DHomMatrix
& rTextureTransform
,
440 const ::tools::Rectangle
& rBounds
,
441 unsigned int nStepCount
)
443 switch( rValues
.meType
)
445 case ::canvas::ParametricPolyPolygon::GradientType::Linear
:
446 fillLinearGradient( rOutDev
,
454 case ::canvas::ParametricPolyPolygon::GradientType::Elliptical
:
455 case ::canvas::ParametricPolyPolygon::GradientType::Rectangular
:
456 fillPolygonalGradient( rOutDev
,
465 ENSURE_OR_THROW( false,
466 "CanvasHelper::doGradientFill(): Unexpected case" );
470 int numColorSteps( const ::Color
& rColor1
, const ::Color
& rColor2
)
473 std::abs( rColor1
.GetRed() - rColor2
.GetRed() ),
475 std::abs( rColor1
.GetGreen() - rColor2
.GetGreen() ),
476 std::abs( rColor1
.GetBlue() - rColor2
.GetBlue() ) ) );
479 bool gradientFill( OutputDevice
& rOutDev
,
480 OutputDevice
* p2ndOutDev
,
481 const ::canvas::ParametricPolyPolygon::Values
& rValues
,
482 const std::vector
< ::Color
>& rColors
,
483 const ::tools::PolyPolygon
& rPoly
,
484 const rendering::ViewState
& viewState
,
485 const rendering::RenderState
& renderState
,
486 const rendering::Texture
& texture
,
489 // TODO(T2): It is maybe necessary to lock here, should
490 // maGradientPoly someday cease to be const. But then, beware of
491 // deadlocks, canvashelper calls this method with locked own
497 for( size_t i
=0; i
<rColors
.size()-1; ++i
)
498 nColorSteps
+= numColorSteps(rColors
[i
],rColors
[i
+1]);
500 ::basegfx::B2DHomMatrix aTotalTransform
;
501 const int nStepCount
=
502 ::canvas::tools::calcGradientStepCount(aTotalTransform
,
508 rOutDev
.SetLineColor();
510 // determine maximal bound rect of texture-filled
512 const ::tools::Rectangle
aPolygonDeviceRectOrig(
513 rPoly
.GetBoundRect() );
515 if( tools::isRectangle( rPoly
) )
517 // use optimized output path
520 // this distinction really looks like a
521 // micro-optimization, but in fact greatly speeds up
522 // especially complex gradients. That's because when using
523 // clipping, we can output polygons instead of
524 // poly-polygons, and don't have to output the gradient
527 rOutDev
.Push( vcl::PushFlags::CLIPREGION
);
528 rOutDev
.IntersectClipRegion( aPolygonDeviceRectOrig
);
529 doGradientFill( rOutDev
,
533 aPolygonDeviceRectOrig
,
537 if( p2ndOutDev
&& nTransparency
< 253 )
539 // HACK. Normally, CanvasHelper does not care about
540 // actually what mp2ndOutDev is... well, here we do &
541 // assume a 1bpp target - everything beyond 97%
542 // transparency is fully transparent
543 p2ndOutDev
->SetFillColor( COL_BLACK
);
544 p2ndOutDev
->DrawRect( aPolygonDeviceRectOrig
);
549 const vcl::Region
aPolyClipRegion( rPoly
);
551 rOutDev
.Push( vcl::PushFlags::CLIPREGION
);
552 rOutDev
.IntersectClipRegion( aPolyClipRegion
);
554 doGradientFill( rOutDev
,
558 aPolygonDeviceRectOrig
,
562 if( p2ndOutDev
&& nTransparency
< 253 )
564 // HACK. Normally, CanvasHelper does not care about
565 // actually what mp2ndOutDev is... well, here we do &
566 // assume a 1bpp target - everything beyond 97%
567 // transparency is fully transparent
568 p2ndOutDev
->SetFillColor( COL_BLACK
);
569 p2ndOutDev
->DrawPolyPolygon( rPoly
);
573 #ifdef DEBUG_CANVAS_CANVASHELPER_TEXTUREFILL
576 ::basegfx::B2DRectangle
aRect(0.0, 0.0, 1.0, 1.0);
577 ::basegfx::B2DHomMatrix aTextureTransform
;
578 ::basegfx::B2DRectangle aTextureDeviceRect
= ::canvas::tools::calcTransformedRectBounds(
581 rOutDev
.SetLineColor( COL_RED
);
582 rOutDev
.SetFillColor();
583 rOutDev
.DrawRect( vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect
) );
585 rOutDev
.SetLineColor( COL_BLUE
);
586 ::tools::Polygon
aPoly1(
587 vcl::unotools::rectangleFromB2DRectangle( aRect
));
588 ::basegfx::B2DPolygon
aPoly2( aPoly1
.getB2DPolygon() );
589 aPoly2
.transform( aTextureTransform
);
590 ::tools::Polygon
aPoly3( aPoly2
);
591 rOutDev
.DrawPolygon( aPoly3
);
599 uno::Reference
< rendering::XCachedPrimitive
> CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas
* pCanvas
,
600 const uno::Reference
< rendering::XPolyPolygon2D
>& xPolyPolygon
,
601 const rendering::ViewState
& viewState
,
602 const rendering::RenderState
& renderState
,
603 const uno::Sequence
< rendering::Texture
>& textures
)
605 ENSURE_ARG_OR_THROW( xPolyPolygon
.is(),
606 "CanvasHelper::fillPolyPolygon(): polygon is NULL");
607 ENSURE_ARG_OR_THROW( textures
.hasElements(),
608 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
610 if( mpOutDevProvider
)
612 tools::OutDevStateKeeper
aStateKeeper( mpProtectedOutDevProvider
);
614 const int nTransparency( setupOutDevState( viewState
, renderState
, IGNORE_COLOR
) );
615 ::tools::PolyPolygon
aPolyPoly( tools::mapPolyPolygon(
616 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon
),
617 viewState
, renderState
) );
619 // TODO(F1): Multi-texturing
620 if( textures
[0].Gradient
.is() )
622 // try to cast XParametricPolyPolygon2D reference to
623 // our implementation class.
624 ::canvas::ParametricPolyPolygon
* pGradient
=
625 dynamic_cast< ::canvas::ParametricPolyPolygon
* >( textures
[0].Gradient
.get() );
627 if( pGradient
&& pGradient
->getValues().maColors
.hasElements() )
629 // copy state from Gradient polypoly locally
630 // (given object might change!)
631 const ::canvas::ParametricPolyPolygon::Values
aValues(
632 pGradient
->getValues() );
634 if( aValues
.maColors
.getLength() < 2 )
636 rendering::RenderState aTempState
=renderState
;
637 aTempState
.DeviceColor
= aValues
.maColors
[0];
638 fillPolyPolygon(pCanvas
, xPolyPolygon
, viewState
, aTempState
);
642 std::vector
< ::Color
> aColors(aValues
.maColors
.getLength());
643 std::transform(&aValues
.maColors
[0],
644 &aValues
.maColors
[0]+aValues
.maColors
.getLength(),
646 [](const uno::Sequence
< double >& aColor
) {
647 return vcl::unotools::stdColorSpaceSequenceToColor( aColor
);
650 // TODO(E1): Return value
651 // TODO(F1): FillRule
652 gradientFill( mpOutDevProvider
->getOutDev(),
653 mp2ndOutDevProvider
? &mp2ndOutDevProvider
->getOutDev() : nullptr,
665 // TODO(F1): The generic case is missing here
666 ENSURE_OR_THROW( false,
667 "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
670 else if( textures
[0].Bitmap
.is() )
672 geometry::IntegerSize2D
aBmpSize( textures
[0].Bitmap
->getSize() );
674 ENSURE_ARG_OR_THROW( aBmpSize
.Width
!= 0 &&
675 aBmpSize
.Height
!= 0,
676 "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
678 // determine maximal bound rect of texture-filled
680 const ::tools::Rectangle
aPolygonDeviceRect(
681 aPolyPoly
.GetBoundRect() );
684 // first of all, determine whether we have a
685 // drawBitmap() in disguise
686 // =========================================
688 const bool bRectangularPolygon( tools::isRectangle( aPolyPoly
) );
690 ::basegfx::B2DHomMatrix aTotalTransform
;
691 ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform
,
694 ::basegfx::B2DHomMatrix aTextureTransform
;
695 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform
,
696 textures
[0].AffineTransform
);
698 aTotalTransform
*= aTextureTransform
;
700 const ::basegfx::B2DRectangle
aRect(0.0, 0.0, 1.0, 1.0);
701 ::basegfx::B2DRectangle aTextureDeviceRect
= ::canvas::tools::calcTransformedRectBounds(
705 const ::tools::Rectangle
aIntegerTextureDeviceRect(
706 vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect
) );
708 if( bRectangularPolygon
&&
709 aIntegerTextureDeviceRect
== aPolygonDeviceRect
)
711 rendering::RenderState
aLocalState( renderState
);
712 ::canvas::tools::appendToRenderState(aLocalState
,
714 ::basegfx::B2DHomMatrix aScaleCorrection
;
715 aScaleCorrection
.scale( 1.0/aBmpSize
.Width
,
716 1.0/aBmpSize
.Height
);
717 ::canvas::tools::appendToRenderState(aLocalState
,
720 // need alpha modulation?
721 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
724 // setup alpha modulation values
725 aLocalState
.DeviceColor
.realloc(4);
726 double* pColor
= aLocalState
.DeviceColor
.getArray();
730 pColor
[3] = textures
[0].Alpha
;
732 return drawBitmapModulated( pCanvas
,
739 return drawBitmap( pCanvas
,
747 // No easy mapping to drawBitmap() - calculate
748 // texturing parameters
749 // ===========================================
751 BitmapEx
aBmpEx( tools::bitmapExFromXBitmap( textures
[0].Bitmap
) );
753 // scale down bitmap to [0,1]x[0,1] rect, as required
754 // from the XCanvas interface.
755 ::basegfx::B2DHomMatrix aScaling
;
756 ::basegfx::B2DHomMatrix aPureTotalTransform
; // pure view*render*texture transform
757 aScaling
.scale( 1.0/aBmpSize
.Width
,
758 1.0/aBmpSize
.Height
);
760 aTotalTransform
= aTextureTransform
* aScaling
;
761 aPureTotalTransform
= aTextureTransform
;
763 // combine with view and render transform
764 ::basegfx::B2DHomMatrix aMatrix
;
765 ::canvas::tools::mergeViewAndRenderTransform(aMatrix
, viewState
, renderState
);
767 // combine all three transformations into one
768 // global texture-to-device-space transformation
769 aTotalTransform
*= aMatrix
;
770 aPureTotalTransform
*= aMatrix
;
772 // analyze transformation, and setup an
773 // appropriate GraphicObject
774 ::basegfx::B2DVector aScale
;
775 ::basegfx::B2DPoint aOutputPos
;
778 aTotalTransform
.decompose( aScale
, aOutputPos
, nRotate
, nShearX
);
780 GraphicAttr aGrfAttr
;
781 GraphicObjectSharedPtr pGrfObj
;
783 if( ::basegfx::fTools::equalZero( nShearX
) )
785 // no shear, GraphicObject is enough (the
786 // GraphicObject only supports scaling, rotation
789 // #i75339# don't apply mirror flags, having
790 // negative size values is enough to make
791 // GraphicObject flip the bitmap
793 // The angle has to be mapped from radian to tenths of
794 // degrees with the orientation reversed: [0,2Pi) ->
795 // (3600,0]. Note that the original angle may have
796 // values outside the [0,2Pi) interval.
797 const double nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg
<10>(nRotate
));
798 aGrfAttr
.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees
)) );
800 pGrfObj
= std::make_shared
<GraphicObject
>( aBmpEx
);
804 // modify output position, to account for the fact
805 // that transformBitmap() always normalizes its output
806 // bitmap into the smallest enclosing box.
807 ::basegfx::B2DRectangle aDestRect
= ::canvas::tools::calcTransformedRectBounds(
808 ::basegfx::B2DRectangle(0,
814 aOutputPos
.setX( aDestRect
.getMinX() );
815 aOutputPos
.setY( aDestRect
.getMinY() );
817 // complex transformation, use generic affine bitmap
819 aBmpEx
= tools::transformBitmap( aBmpEx
,
822 pGrfObj
= std::make_shared
<GraphicObject
>( aBmpEx
);
824 // clear scale values, generated bitmap already
826 aScale
.setX( 1.0 ); aScale
.setY( 1.0 );
828 // update bitmap size, bitmap has changed above.
829 aBmpSize
= vcl::unotools::integerSize2DFromSize(aBmpEx
.GetSizePixel());
833 // render texture tiled into polygon
834 // =================================
836 // calc device space direction vectors. We employ
837 // the following approach for tiled output: the
838 // texture bitmap is output in texture space
839 // x-major order, i.e. tile neighbors in texture
840 // space x direction are rendered back-to-back in
841 // device coordinate space (after the full device
842 // transformation). Thus, the aNextTile* vectors
843 // denote the output position updates in device
844 // space, to get from one tile to the next.
845 ::basegfx::B2DVector
aNextTileX( 1.0, 0.0 );
846 ::basegfx::B2DVector
aNextTileY( 0.0, 1.0 );
847 aNextTileX
*= aPureTotalTransform
;
848 aNextTileY
*= aPureTotalTransform
;
850 ::basegfx::B2DHomMatrix
aInverseTextureTransform( aPureTotalTransform
);
852 ENSURE_ARG_OR_THROW( aInverseTextureTransform
.isInvertible(),
853 "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
855 aInverseTextureTransform
.invert();
857 // calc bound rect of extended texture area in
858 // device coordinates. Therefore, we first calc
859 // the area of the polygon bound rect in texture
860 // space. To maintain texture phase, this bound
861 // rect is then extended to integer coordinates
862 // (extended, because shrinking might leave some
863 // inner polygon areas unfilled).
864 // Finally, the bound rect is transformed back to
865 // device coordinate space, were we determine the
866 // start point from it.
867 ::basegfx::B2DRectangle aTextureSpacePolygonRect
= ::canvas::tools::calcTransformedRectBounds(
868 vcl::unotools::b2DRectangleFromRectangle(aPolygonDeviceRect
),
869 aInverseTextureTransform
);
871 // calc left, top of extended polygon rect in
872 // texture space, create one-texture instance rect
873 // from it (i.e. rect from start point extending
874 // 1.0 units to the right and 1.0 units to the
875 // bottom). Note that the rounding employed here
876 // is a bit subtle, since we need to round up/down
877 // as _soon_ as any fractional amount is
878 // encountered. This is to ensure that the full
879 // polygon area is filled with texture tiles.
880 const sal_Int32
nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect
.getMinX() ) );
881 const sal_Int32
nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect
.getMinY() ) );
882 const sal_Int32
nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect
.getMaxX() ) );
883 const sal_Int32
nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect
.getMaxY() ) );
884 const ::basegfx::B2DRectangle
aSingleTextureRect(
889 // and convert back to device space
890 ::basegfx::B2DRectangle aSingleDeviceTextureRect
= ::canvas::tools::calcTransformedRectBounds(
892 aPureTotalTransform
);
894 const ::Point
aPtRepeat( vcl::unotools::pointFromB2DPoint(
895 aSingleDeviceTextureRect
.getMinimum() ) );
896 const ::Size
aSz( ::basegfx::fround
<::tools::Long
>( aScale
.getX() * aBmpSize
.Width
),
897 ::basegfx::fround
<::tools::Long
>( aScale
.getY() * aBmpSize
.Height
) );
898 const ::Size
aIntegerNextTileX( vcl::unotools::sizeFromB2DSize(aNextTileX
) );
899 const ::Size
aIntegerNextTileY( vcl::unotools::sizeFromB2DSize(aNextTileY
) );
901 const ::Point
aPt( textures
[0].RepeatModeX
== rendering::TexturingMode::NONE
?
902 ::basegfx::fround
<::tools::Long
>( aOutputPos
.getX() ) : aPtRepeat
.X(),
903 textures
[0].RepeatModeY
== rendering::TexturingMode::NONE
?
904 ::basegfx::fround
<::tools::Long
>( aOutputPos
.getY() ) : aPtRepeat
.Y() );
905 const sal_Int32
nTilesX( textures
[0].RepeatModeX
== rendering::TexturingMode::NONE
?
907 const sal_Int32
nTilesY( textures
[0].RepeatModeX
== rendering::TexturingMode::NONE
?
910 OutputDevice
& rOutDev( mpOutDevProvider
->getOutDev() );
912 if( bRectangularPolygon
)
914 // use optimized output path
917 // this distinction really looks like a
918 // micro-optimization, but in fact greatly speeds up
919 // especially complex fills. That's because when using
920 // clipping, we can output polygons instead of
921 // poly-polygons, and don't have to output the gradient
924 // setup alpha modulation
925 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
928 // TODO(F1): Note that the GraphicManager has
929 // a subtle difference in how it calculates
930 // the resulting alpha value: it's using the
931 // inverse alpha values (i.e. 'transparency'),
932 // and calculates transOrig + transModulate,
933 // instead of transOrig + transModulate -
934 // transOrig*transModulate (which would be
935 // equivalent to the origAlpha*modulateAlpha
936 // the DX canvas performs)
938 static_cast< sal_uInt8
>(
939 ::basegfx::fround( 255.0 * textures
[0].Alpha
) ) );
942 rOutDev
.IntersectClipRegion( aPolygonDeviceRect
);
943 textureFill( rOutDev
,
953 if( mp2ndOutDevProvider
)
955 OutputDevice
& r2ndOutDev( mp2ndOutDevProvider
->getOutDev() );
956 r2ndOutDev
.IntersectClipRegion( aPolygonDeviceRect
);
957 textureFill( r2ndOutDev
,
970 // output texture the hard way: XORing out the
972 // ===========================================
974 if( !::rtl::math::approxEqual( textures
[0].Alpha
,
977 // uh-oh. alpha blending is required,
978 // cannot do direct XOR, but have to
979 // prepare the filled polygon within a
981 ScopedVclPtrInstance
< VirtualDevice
> pVDev( rOutDev
);
982 pVDev
->SetOutputSizePixel( aPolygonDeviceRect
.GetSize() );
984 // shift output to origin of VDev
985 const ::Point
aOutPos( aPt
- aPolygonDeviceRect
.TopLeft() );
986 aPolyPoly
.Translate( ::Point( -aPolygonDeviceRect
.Left(),
987 -aPolygonDeviceRect
.Top() ) );
989 const vcl::Region
aPolyClipRegion( aPolyPoly
);
991 pVDev
->SetClipRegion( aPolyClipRegion
);
1002 // output VDev content alpha-blended to
1004 const ::Point aEmptyPoint
;
1005 BitmapEx
aContentBmp(
1006 pVDev
->GetBitmapEx( aEmptyPoint
,
1007 pVDev
->GetOutputSizePixel() ) );
1009 sal_uInt8
nCol( static_cast< sal_uInt8
>(
1010 ::basegfx::fround( 255.0*( 1.0 - textures
[0].Alpha
) ) ) );
1011 AlphaMask
aAlpha( pVDev
->GetOutputSizePixel(),
1014 BitmapEx
aOutputBmpEx( aContentBmp
.GetBitmap(), aAlpha
);
1015 rOutDev
.DrawBitmapEx( aPolygonDeviceRect
.TopLeft(),
1018 if( mp2ndOutDevProvider
)
1019 mp2ndOutDevProvider
->getOutDev().DrawBitmapEx( aPolygonDeviceRect
.TopLeft(),
1024 const vcl::Region
aPolyClipRegion( aPolyPoly
);
1026 rOutDev
.Push( vcl::PushFlags::CLIPREGION
);
1027 rOutDev
.IntersectClipRegion( aPolyClipRegion
);
1029 textureFill( rOutDev
,
1040 if( mp2ndOutDevProvider
)
1042 OutputDevice
& r2ndOutDev( mp2ndOutDevProvider
->getOutDev() );
1043 r2ndOutDev
.Push( vcl::PushFlags::CLIPREGION
);
1045 r2ndOutDev
.IntersectClipRegion( aPolyClipRegion
);
1046 textureFill( r2ndOutDev
,
1063 // TODO(P1): Provide caching here.
1064 return uno::Reference
< rendering::XCachedPrimitive
>(nullptr);
1069 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */