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