Move setting of LD_LIBRARY_PATH closer to invocation of cppunittester
[LibreOffice.git] / canvas / source / vcl / canvashelper_texturefill.cxx
blob2a1ec6b88c9abcf4dd710b8cee7b1722111e7f62
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <cstdlib>
23 #include <tuple>
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;
53 namespace vclcanvas
55 namespace
57 bool textureFill( OutputDevice& rOutDev,
58 const GraphicObject& rGraphic,
59 const ::Point& rPosPixel,
60 const ::Size& rNextTileX,
61 const ::Size& rNextTileY,
62 sal_Int32 nTilesX,
63 sal_Int32 nTilesY,
64 const ::Size& rTileSize,
65 const GraphicAttr& rAttr)
67 bool bRet( false );
68 Point aCurrPos;
69 int nX, nY;
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,
81 aCurrPos,
82 rTileSize,
83 &rAttr);
85 aCurrPos.AdjustX(rNextTileX.Width() );
86 aCurrPos.AdjustY(rNextTileX.Height() );
90 return bRet;
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
107 // the bound rect
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:
127 // _______
128 // | | |
129 // -> | | | ...
130 // | | |
131 // -------
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,
139 aLeftBottom,
140 aRightTop,
141 aRightBottom,
142 vcl::unotools::b2DRectangleFromRectangle(rBounds) );
145 // render gradient
146 // ===============
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 );
168 return;
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 );
186 return;
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
227 // gradients.
228 if( (rColors.size() % 2) != (nStepCount % 2) )
229 ++nStepCount;
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;
240 double fAlpha;
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
284 // the 'right'.
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,
347 0.0 );
349 else if( nAspectRatio < 1.0 )
351 // width < height case
352 aInnerPolygonTransformMatrix.scale( 0.0,
353 1.0 - nAspectRatio );
355 else
357 // isotrophic case
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:
377 // | |
378 // |-------|-------|-------|
379 // | |
380 // 3 2 1 0
382 // This yields 4 tick marks, where 0 is never attained (since
383 // zero-area polygons typically don't display perceivable
384 // color).
385 ++nStepCount;
387 rOutDev.SetLineColor();
389 basegfx::utils::KeyStopLerp aLerper(rValues.maStops);
391 // fill background
392 rOutDev.SetFillColor( rColors.front() );
393 rOutDev.DrawRect( rBounds );
395 // render polygon
396 // ==============
398 for( unsigned int i=1,p; i<nStepCount; ++i )
400 const double fT( i/double(nStepCount) );
402 std::ptrdiff_t nIndex;
403 double fAlpha;
404 std::tie(nIndex,fAlpha)=aLerper.lerp(fT);
406 // lerp color
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
431 // is faster!
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,
447 rTextureTransform,
448 rBounds,
449 nStepCount,
450 rValues,
451 rColors );
452 break;
454 case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
455 case ::canvas::ParametricPolyPolygon::GradientType::Rectangular:
456 fillPolygonalGradient( rOutDev,
457 rTextureTransform,
458 rBounds,
459 nStepCount,
460 rValues,
461 rColors );
462 break;
464 default:
465 ENSURE_OR_THROW( false,
466 "CanvasHelper::doGradientFill(): Unexpected case" );
470 int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 )
472 return std::max(
473 std::abs( rColor1.GetRed() - rColor2.GetRed() ),
474 std::max(
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,
487 int nTransparency )
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
492 // mutex.
494 // calc step size
496 int nColorSteps = 0;
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,
503 viewState,
504 renderState,
505 texture,
506 nColorSteps);
508 rOutDev.SetLineColor();
510 // determine maximal bound rect of texture-filled
511 // polygon
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
525 // twice for XOR
527 rOutDev.Push( vcl::PushFlags::CLIPREGION );
528 rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
529 doGradientFill( rOutDev,
530 rValues,
531 rColors,
532 aTotalTransform,
533 aPolygonDeviceRectOrig,
534 nStepCount );
535 rOutDev.Pop();
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 );
547 else
549 const vcl::Region aPolyClipRegion( rPoly );
551 rOutDev.Push( vcl::PushFlags::CLIPREGION );
552 rOutDev.IntersectClipRegion( aPolyClipRegion );
554 doGradientFill( rOutDev,
555 rValues,
556 rColors,
557 aTotalTransform,
558 aPolygonDeviceRectOrig,
559 nStepCount );
560 rOutDev.Pop();
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
574 // extra-verbosity
576 ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
577 ::basegfx::B2DHomMatrix aTextureTransform;
578 ::basegfx::B2DRectangle aTextureDeviceRect = ::canvas::tools::calcTransformedRectBounds(
579 aRect,
580 aTextureTransform );
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 );
593 #endif
595 return true;
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);
640 else
642 std::vector< ::Color > aColors(aValues.maColors.getLength());
643 std::transform(&aValues.maColors[0],
644 &aValues.maColors[0]+aValues.maColors.getLength(),
645 aColors.begin(),
646 [](const uno::Sequence< double >& aColor) {
647 return vcl::unotools::stdColorSpaceSequenceToColor( aColor );
648 } );
650 // TODO(E1): Return value
651 // TODO(F1): FillRule
652 gradientFill( mpOutDevProvider->getOutDev(),
653 mp2ndOutDevProvider ? &mp2ndOutDevProvider->getOutDev() : nullptr,
654 aValues,
655 aColors,
656 aPolyPoly,
657 viewState,
658 renderState,
659 textures[0],
660 nTransparency );
663 else
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
679 // polygon
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,
692 viewState,
693 renderState);
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(
702 aRect,
703 aTotalTransform );
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,
713 aTextureTransform);
714 ::basegfx::B2DHomMatrix aScaleCorrection;
715 aScaleCorrection.scale( 1.0/aBmpSize.Width,
716 1.0/aBmpSize.Height );
717 ::canvas::tools::appendToRenderState(aLocalState,
718 aScaleCorrection);
720 // need alpha modulation?
721 if( !::rtl::math::approxEqual( textures[0].Alpha,
722 1.0 ) )
724 // setup alpha modulation values
725 aLocalState.DeviceColor.realloc(4);
726 double* pColor = aLocalState.DeviceColor.getArray();
727 pColor[0] =
728 pColor[1] =
729 pColor[2] = 0.0;
730 pColor[3] = textures[0].Alpha;
732 return drawBitmapModulated( pCanvas,
733 textures[0].Bitmap,
734 viewState,
735 aLocalState );
737 else
739 return drawBitmap( pCanvas,
740 textures[0].Bitmap,
741 viewState,
742 aLocalState );
745 else
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;
776 double nRotate;
777 double nShearX;
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
787 // and translation)
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 );
802 else
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,
810 aBmpSize.Width,
811 aBmpSize.Height),
812 aMatrix );
814 aOutputPos.setX( aDestRect.getMinX() );
815 aOutputPos.setY( aDestRect.getMinY() );
817 // complex transformation, use generic affine bitmap
818 // transformation
819 aBmpEx = tools::transformBitmap( aBmpEx,
820 aTotalTransform);
822 pGrfObj = std::make_shared<GraphicObject>( aBmpEx );
824 // clear scale values, generated bitmap already
825 // contains scaling
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(
885 nX1, nY1,
886 nX1 + 1.0,
887 nY1 + 1.0 );
889 // and convert back to device space
890 ::basegfx::B2DRectangle aSingleDeviceTextureRect = ::canvas::tools::calcTransformedRectBounds(
891 aSingleTextureRect,
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 ?
906 1 : nX2 - nX1 );
907 const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
908 1 : nY2 - nY1 );
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
922 // twice for XOR
924 // setup alpha modulation
925 if( !::rtl::math::approxEqual( textures[0].Alpha,
926 1.0 ) )
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)
937 aGrfAttr.SetAlpha(
938 static_cast< sal_uInt8 >(
939 ::basegfx::fround( 255.0 * textures[0].Alpha ) ) );
942 rOutDev.IntersectClipRegion( aPolygonDeviceRect );
943 textureFill( rOutDev,
944 *pGrfObj,
945 aPt,
946 aIntegerNextTileX,
947 aIntegerNextTileY,
948 nTilesX,
949 nTilesY,
950 aSz,
951 aGrfAttr );
953 if( mp2ndOutDevProvider )
955 OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
956 r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
957 textureFill( r2ndOutDev,
958 *pGrfObj,
959 aPt,
960 aIntegerNextTileX,
961 aIntegerNextTileY,
962 nTilesX,
963 nTilesY,
964 aSz,
965 aGrfAttr );
968 else
970 // output texture the hard way: XORing out the
971 // polygon
972 // ===========================================
974 if( !::rtl::math::approxEqual( textures[0].Alpha,
975 1.0 ) )
977 // uh-oh. alpha blending is required,
978 // cannot do direct XOR, but have to
979 // prepare the filled polygon within a
980 // VDev
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 );
992 textureFill( *pVDev,
993 *pGrfObj,
994 aOutPos,
995 aIntegerNextTileX,
996 aIntegerNextTileY,
997 nTilesX,
998 nTilesY,
999 aSz,
1000 aGrfAttr );
1002 // output VDev content alpha-blended to
1003 // target position.
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(),
1012 &nCol );
1014 BitmapEx aOutputBmpEx( aContentBmp.GetBitmap(), aAlpha );
1015 rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1016 aOutputBmpEx );
1018 if( mp2ndOutDevProvider )
1019 mp2ndOutDevProvider->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1020 aOutputBmpEx );
1022 else
1024 const vcl::Region aPolyClipRegion( aPolyPoly );
1026 rOutDev.Push( vcl::PushFlags::CLIPREGION );
1027 rOutDev.IntersectClipRegion( aPolyClipRegion );
1029 textureFill( rOutDev,
1030 *pGrfObj,
1031 aPt,
1032 aIntegerNextTileX,
1033 aIntegerNextTileY,
1034 nTilesX,
1035 nTilesY,
1036 aSz,
1037 aGrfAttr );
1038 rOutDev.Pop();
1040 if( mp2ndOutDevProvider )
1042 OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() );
1043 r2ndOutDev.Push( vcl::PushFlags::CLIPREGION );
1045 r2ndOutDev.IntersectClipRegion( aPolyClipRegion );
1046 textureFill( r2ndOutDev,
1047 *pGrfObj,
1048 aPt,
1049 aIntegerNextTileX,
1050 aIntegerNextTileY,
1051 nTilesX,
1052 nTilesY,
1053 aSz,
1054 aGrfAttr );
1055 r2ndOutDev.Pop();
1063 // TODO(P1): Provide caching here.
1064 return uno::Reference< rendering::XCachedPrimitive >(nullptr);
1069 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */