Branch libreoffice-5-0-4
[LibreOffice.git] / canvas / source / vcl / canvashelper_texturefill.cxx
blobcf628c7503769a5158c7b9ca53f083bc09589da9
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 .
21 #include <canvas/debug.hxx>
22 #include <tools/diagnose_ex.h>
24 #include <rtl/math.hxx>
26 #include <com/sun/star/rendering/TextDirection.hpp>
27 #include <com/sun/star/rendering/TexturingMode.hpp>
28 #include <com/sun/star/rendering/PathCapType.hpp>
29 #include <com/sun/star/rendering/PathJoinType.hpp>
31 #include <tools/poly.hxx>
32 #include <vcl/window.hxx>
33 #include <vcl/bitmapex.hxx>
34 #include <vcl/bmpacc.hxx>
35 #include <vcl/virdev.hxx>
36 #include <vcl/canvastools.hxx>
38 #include <basegfx/matrix/b2dhommatrix.hxx>
39 #include <basegfx/range/b2drectangle.hxx>
40 #include <basegfx/point/b2dpoint.hxx>
41 #include <basegfx/vector/b2dsize.hxx>
42 #include <basegfx/polygon/b2dpolygon.hxx>
43 #include <basegfx/polygon/b2dpolygontools.hxx>
44 #include <basegfx/polygon/b2dpolypolygontools.hxx>
45 #include <basegfx/polygon/b2dlinegeometry.hxx>
46 #include <basegfx/tools/tools.hxx>
47 #include <basegfx/tools/lerp.hxx>
48 #include <basegfx/tools/keystoplerp.hxx>
49 #include <basegfx/tools/canvastools.hxx>
50 #include <basegfx/numeric/ftools.hxx>
52 #include <comphelper/sequence.hxx>
54 #include <canvas/canvastools.hxx>
55 #include <canvas/parametricpolypolygon.hxx>
57 #include <boost/bind.hpp>
58 #include <boost/tuple/tuple.hpp>
60 #include "spritecanvas.hxx"
61 #include "canvashelper.hxx"
62 #include "impltools.hxx"
65 using namespace ::com::sun::star;
67 namespace vclcanvas
69 namespace
71 bool textureFill( OutputDevice& rOutDev,
72 GraphicObject& rGraphic,
73 const ::Point& rPosPixel,
74 const ::Size& rNextTileX,
75 const ::Size& rNextTileY,
76 sal_Int32 nTilesX,
77 sal_Int32 nTilesY,
78 const ::Size& rTileSize,
79 const GraphicAttr& rAttr)
81 bool bRet( false );
82 Point aCurrPos;
83 int nX, nY;
85 for( nY=0; nY < nTilesY; ++nY )
87 aCurrPos.X() = rPosPixel.X() + nY*rNextTileY.Width();
88 aCurrPos.Y() = rPosPixel.Y() + nY*rNextTileY.Height();
90 for( nX=0; nX < nTilesX; ++nX )
92 // update return value. This method should return true, if
93 // at least one of the looped Draws succeeded.
94 bRet |= rGraphic.Draw( &rOutDev,
95 aCurrPos,
96 rTileSize,
97 &rAttr );
99 aCurrPos.X() += rNextTileX.Width();
100 aCurrPos.Y() += rNextTileX.Height();
104 return bRet;
108 /** Fill linear or axial gradient
110 Since most of the code for linear and axial gradients are
111 the same, we've a unified method here
113 void fillLinearGradient( OutputDevice& rOutDev,
114 const ::basegfx::B2DHomMatrix& rTextureTransform,
115 const ::Rectangle& rBounds,
116 unsigned int nStepCount,
117 const ::canvas::ParametricPolyPolygon::Values& rValues,
118 const std::vector< ::Color >& rColors )
120 // determine general position of gradient in relation to
121 // the bound rect
122 // =====================================================
124 ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
125 ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
126 ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
127 ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
129 aLeftTop *= rTextureTransform;
130 aLeftBottom *= rTextureTransform;
131 aRightTop *= rTextureTransform;
132 aRightBottom*= rTextureTransform;
134 // calc length of bound rect diagonal
135 const ::basegfx::B2DVector aBoundRectDiagonal(
136 vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) -
137 vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) );
138 const double nDiagonalLength( aBoundRectDiagonal.getLength() );
140 // create direction of gradient:
141 // _______
142 // | | |
143 // -> | | | ...
144 // | | |
145 // -------
146 ::basegfx::B2DVector aDirection( aRightTop - aLeftTop );
147 aDirection.normalize();
149 // now, we potentially have to enlarge our gradient area
150 // atop and below the transformed [0,1]x[0,1] unit rect,
151 // for the gradient to fill the complete bound rect.
152 ::basegfx::tools::infiniteLineFromParallelogram( aLeftTop,
153 aLeftBottom,
154 aRightTop,
155 aRightBottom,
156 vcl::unotools::b2DRectangleFromRectangle( rBounds ) );
159 // render gradient
160 // ===============
162 // for linear gradients, it's easy to render
163 // non-overlapping polygons: just split the gradient into
164 // nStepCount small strips. Prepare the strip now.
166 // For performance reasons, we create a temporary VCL
167 // polygon here, keep it all the way and only change the
168 // vertex values in the loop below (as ::Polygon is a
169 // pimpl class, creating one every loop turn would really
170 // stress the mem allocator)
171 ::Polygon aTempPoly( static_cast<sal_uInt16>(5) );
173 OSL_ENSURE( nStepCount >= 3,
174 "fillLinearGradient(): stepcount smaller than 3" );
177 // fill initial strip (extending two times the bound rect's
178 // diagonal to the 'left'
181 // calculate left edge, by moving left edge of the
182 // gradient rect two times the bound rect's diagonal to
183 // the 'left'. Since we postpone actual rendering into the
184 // loop below, we set the _right_ edge here, which will be
185 // readily copied into the left edge in the loop below
186 const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection );
187 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ),
188 ::basegfx::fround( rPoint1.getY() ) );
190 const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection );
191 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ),
192 ::basegfx::fround( rPoint2.getY() ) );
195 // iteratively render all other strips
198 // ensure that nStepCount matches color stop parity, to
199 // have a well-defined middle color e.g. for axial
200 // gradients.
201 if( (rColors.size() % 2) != (nStepCount % 2) )
202 ++nStepCount;
204 rOutDev.SetLineColor();
206 basegfx::tools::KeyStopLerp aLerper(rValues.maStops);
208 // only iterate nStepCount-1 steps, as the last strip is
209 // explicitly painted below
210 for( unsigned int i=0; i<nStepCount-1; ++i )
212 std::ptrdiff_t nIndex;
213 double fAlpha;
214 boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount);
216 rOutDev.SetFillColor(
217 Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
218 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
219 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
221 // copy right egde of polygon to left edge (and also
222 // copy the closing point)
223 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
224 aTempPoly[3] = aTempPoly[2];
226 // calculate new right edge, from interpolating
227 // between start and end line. Note that i is
228 // increased by one, to account for the fact that we
229 // calculate the right border here (whereas the fill
230 // color is governed by the left edge)
231 const ::basegfx::B2DPoint& rPoint3(
232 (nStepCount - i-1)/double(nStepCount)*aLeftTop +
233 (i+1)/double(nStepCount)*aRightTop );
234 aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ),
235 ::basegfx::fround( rPoint3.getY() ) );
237 const ::basegfx::B2DPoint& rPoint4(
238 (nStepCount - i-1)/double(nStepCount)*aLeftBottom +
239 (i+1)/double(nStepCount)*aRightBottom );
240 aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ),
241 ::basegfx::fround( rPoint4.getY() ) );
243 rOutDev.DrawPolygon( aTempPoly );
246 // fill final strip (extending two times the bound rect's
247 // diagonal to the 'right'
250 // copy right egde of polygon to left edge (and also
251 // copy the closing point)
252 aTempPoly[0] = aTempPoly[4] = aTempPoly[1];
253 aTempPoly[3] = aTempPoly[2];
255 // calculate new right edge, by moving right edge of the
256 // gradient rect two times the bound rect's diagonal to
257 // the 'right'.
258 const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection );
259 aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ),
260 ::basegfx::fround( rPoint3.getY() ) );
262 const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection );
263 aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ),
264 ::basegfx::fround( rPoint4.getY() ) );
266 rOutDev.SetFillColor( rColors.back() );
268 rOutDev.DrawPolygon( aTempPoly );
271 void fillPolygonalGradient( OutputDevice& rOutDev,
272 const ::basegfx::B2DHomMatrix& rTextureTransform,
273 const ::Rectangle& rBounds,
274 unsigned int nStepCount,
275 bool bFillNonOverlapping,
276 const ::canvas::ParametricPolyPolygon::Values& rValues,
277 const std::vector< ::Color >& rColors )
279 const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly );
281 ENSURE_OR_THROW( rGradientPoly.count() > 2,
282 "fillPolygonalGradient(): polygon without area given" );
284 // For performance reasons, we create a temporary VCL polygon
285 // here, keep it all the way and only change the vertex values
286 // in the loop below (as ::Polygon is a pimpl class, creating
287 // one every loop turn would really stress the mem allocator)
288 ::basegfx::B2DPolygon aOuterPoly( rGradientPoly );
289 ::basegfx::B2DPolygon aInnerPoly;
291 // subdivide polygon _before_ rendering, would otherwise have
292 // to be performed on every loop turn.
293 if( aOuterPoly.areControlPointsUsed() )
294 aOuterPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aOuterPoly);
296 aInnerPoly = aOuterPoly;
298 // only transform outer polygon _after_ copying it into
299 // aInnerPoly, because inner polygon has to be scaled before
300 // the actual texture transformation takes place
301 aOuterPoly.transform( rTextureTransform );
303 // determine overall transformation for inner polygon (might
304 // have to be prefixed by anisotrophic scaling)
305 ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix;
308 // apply scaling (possibly anisotrophic) to inner polygon
311 // scale inner polygon according to aspect ratio: for
312 // wider-than-tall bounds (nAspectRatio > 1.0), the inner
313 // polygon, representing the gradient focus, must have
314 // non-zero width. Specifically, a bound rect twice as wide as
315 // tall has a focus polygon of half its width.
316 const double nAspectRatio( rValues.mnAspectRatio );
317 if( nAspectRatio > 1.0 )
319 // width > height case
320 aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio,
321 0.0 );
323 else if( nAspectRatio < 1.0 )
325 // width < height case
326 aInnerPolygonTransformMatrix.scale( 0.0,
327 1.0 - nAspectRatio );
329 else
331 // isotrophic case
332 aInnerPolygonTransformMatrix.scale( 0.0, 0.0 );
335 // and finally, add texture transform to it.
336 aInnerPolygonTransformMatrix *= rTextureTransform;
338 // apply final matrix to polygon
339 aInnerPoly.transform( aInnerPolygonTransformMatrix );
342 const sal_uInt32 nNumPoints( aOuterPoly.count() );
343 ::Polygon aTempPoly( static_cast<sal_uInt16>(nNumPoints+1) );
345 // increase number of steps by one: polygonal gradients have
346 // the outermost polygon rendered in rColor2, and the
347 // innermost in rColor1. The innermost polygon will never
348 // have zero area, thus, we must divide the interval into
349 // nStepCount+1 steps. For example, to create 3 steps:
351 // | |
352 // |-------|-------|-------|
353 // | |
354 // 3 2 1 0
356 // This yields 4 tick marks, where 0 is never attained (since
357 // zero-area polygons typically don't display perceivable
358 // color).
359 ++nStepCount;
361 rOutDev.SetLineColor();
363 basegfx::tools::KeyStopLerp aLerper(rValues.maStops);
365 if( !bFillNonOverlapping )
367 // fill background
368 rOutDev.SetFillColor( rColors.front() );
369 rOutDev.DrawRect( rBounds );
371 // render polygon
372 // ==============
374 for( unsigned int i=1,p; i<nStepCount; ++i )
376 const double fT( i/double(nStepCount) );
378 std::ptrdiff_t nIndex;
379 double fAlpha;
380 boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT);
382 // lerp color
383 rOutDev.SetFillColor(
384 Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
385 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
386 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
388 // scale and render polygon, by interpolating between
389 // outer and inner polygon.
391 for( p=0; p<nNumPoints; ++p )
393 const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
394 const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
396 aTempPoly[(sal_uInt16)p] = ::Point(
397 basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
398 basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
401 // close polygon explicitly
402 aTempPoly[(sal_uInt16)p] = aTempPoly[0];
404 // TODO(P1): compare with vcl/source/gdi/outdev4.cxx,
405 // OutputDevice::ImplDrawComplexGradient(), there's a note
406 // that on some VDev's, rendering disjunct poly-polygons
407 // is faster!
408 rOutDev.DrawPolygon( aTempPoly );
411 else
413 // render polygon
414 // ==============
416 // For performance reasons, we create a temporary VCL polygon
417 // here, keep it all the way and only change the vertex values
418 // in the loop below (as ::Polygon is a pimpl class, creating
419 // one every loop turn would really stress the mem allocator)
420 ::tools::PolyPolygon aTempPolyPoly;
421 ::Polygon aTempPoly2( static_cast<sal_uInt16>(nNumPoints+1) );
423 aTempPoly2[0] = rBounds.TopLeft();
424 aTempPoly2[1] = rBounds.TopRight();
425 aTempPoly2[2] = rBounds.BottomRight();
426 aTempPoly2[3] = rBounds.BottomLeft();
427 aTempPoly2[4] = rBounds.TopLeft();
429 aTempPolyPoly.Insert( aTempPoly );
430 aTempPolyPoly.Insert( aTempPoly2 );
432 for( unsigned int i=0,p; i<nStepCount; ++i )
434 const double fT( (i+1)/double(nStepCount) );
436 std::ptrdiff_t nIndex;
437 double fAlpha;
438 boost::tuples::tie(nIndex,fAlpha)=aLerper.lerp(fT);
440 // lerp color
441 rOutDev.SetFillColor(
442 Color( (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)),
443 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)),
444 (sal_uInt8)(basegfx::tools::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) ));
446 #if OSL_DEBUG_LEVEL > 2
447 if( i && !(i % 10) )
448 rOutDev.SetFillColor( COL_RED );
449 #endif
451 // scale and render polygon. Note that here, we
452 // calculate the inner polygon, which is actually the
453 // start of the _next_ color strip. Thus, i+1
455 for( p=0; p<nNumPoints; ++p )
457 const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) );
458 const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) );
460 aTempPoly[(sal_uInt16)p] = ::Point(
461 basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ),
462 basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) );
465 // close polygon explicitly
466 aTempPoly[(sal_uInt16)p] = aTempPoly[0];
468 // swap inner and outer polygon
469 aTempPolyPoly.Replace( aTempPolyPoly.GetObject( 1 ), 0 );
471 if( i+1<nStepCount )
473 // assign new inner polygon. Note that with this
474 // formulation, the internal pimpl objects for both
475 // temp polygons and the polypolygon remain identical,
476 // minimizing heap accesses (only a Polygon wrapper
477 // object is freed and deleted twice during this swap).
478 aTempPolyPoly.Replace( aTempPoly, 1 );
480 else
482 // last, i.e. inner strip. Now, the inner polygon
483 // has zero area anyway, and to not leave holes in
484 // the gradient, finally render a simple polygon:
485 aTempPolyPoly.Remove( 1 );
488 rOutDev.DrawPolyPolygon( aTempPolyPoly );
493 void doGradientFill( OutputDevice& rOutDev,
494 const ::canvas::ParametricPolyPolygon::Values& rValues,
495 const std::vector< ::Color >& rColors,
496 const ::basegfx::B2DHomMatrix& rTextureTransform,
497 const ::Rectangle& rBounds,
498 unsigned int nStepCount,
499 bool bFillNonOverlapping )
501 switch( rValues.meType )
503 case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR:
504 fillLinearGradient( rOutDev,
505 rTextureTransform,
506 rBounds,
507 nStepCount,
508 rValues,
509 rColors );
510 break;
512 case ::canvas::ParametricPolyPolygon::GRADIENT_ELLIPTICAL:
513 // FALLTHROUGH intended
514 case ::canvas::ParametricPolyPolygon::GRADIENT_RECTANGULAR:
515 fillPolygonalGradient( rOutDev,
516 rTextureTransform,
517 rBounds,
518 nStepCount,
519 bFillNonOverlapping,
520 rValues,
521 rColors );
522 break;
524 default:
525 ENSURE_OR_THROW( false,
526 "CanvasHelper::doGradientFill(): Unexpected case" );
530 int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 )
532 return ::std::max(
533 labs( rColor1.GetRed() - rColor2.GetRed() ),
534 ::std::max(
535 labs( rColor1.GetGreen() - rColor2.GetGreen() ),
536 labs( rColor1.GetBlue() - rColor2.GetBlue() ) ) );
539 bool gradientFill( OutputDevice& rOutDev,
540 OutputDevice* p2ndOutDev,
541 const ::canvas::ParametricPolyPolygon::Values& rValues,
542 const std::vector< ::Color >& rColors,
543 const ::tools::PolyPolygon& rPoly,
544 const rendering::ViewState& viewState,
545 const rendering::RenderState& renderState,
546 const rendering::Texture& texture,
547 int nTransparency )
549 // TODO(T2): It is maybe necessary to lock here, should
550 // maGradientPoly someday cease to be const. But then, beware of
551 // deadlocks, canvashelper calls this method with locked own
552 // mutex.
554 // calc step size
556 int nColorSteps = 0;
557 for( size_t i=0; i<rColors.size()-1; ++i )
558 nColorSteps += numColorSteps(rColors[i],rColors[i+1]);
560 ::basegfx::B2DHomMatrix aTotalTransform;
561 const int nStepCount=
562 ::canvas::tools::calcGradientStepCount(aTotalTransform,
563 viewState,
564 renderState,
565 texture,
566 nColorSteps);
568 rOutDev.SetLineColor();
570 // determine maximal bound rect of texture-filled
571 // polygon
572 const ::Rectangle aPolygonDeviceRectOrig(
573 rPoly.GetBoundRect() );
575 if( tools::isRectangle( rPoly ) )
577 // use optimized output path
580 // this distinction really looks like a
581 // micro-optimization, but in fact greatly speeds up
582 // especially complex gradients. That's because when using
583 // clipping, we can output polygons instead of
584 // poly-polygons, and don't have to output the gradient
585 // twice for XOR
587 rOutDev.Push( PushFlags::CLIPREGION );
588 rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig );
589 doGradientFill( rOutDev,
590 rValues,
591 rColors,
592 aTotalTransform,
593 aPolygonDeviceRectOrig,
594 nStepCount,
595 false );
596 rOutDev.Pop();
598 if( p2ndOutDev && nTransparency < 253 )
600 // HACK. Normally, CanvasHelper does not care about
601 // actually what mp2ndOutDev is... well, here we do &
602 // assume a 1bpp target - everything beyond 97%
603 // transparency is fully transparent
604 p2ndOutDev->SetFillColor( COL_BLACK );
605 p2ndOutDev->DrawRect( aPolygonDeviceRectOrig );
608 else
610 const vcl::Region aPolyClipRegion( rPoly );
612 rOutDev.Push( PushFlags::CLIPREGION );
613 rOutDev.SetClipRegion( aPolyClipRegion );
615 doGradientFill( rOutDev,
616 rValues,
617 rColors,
618 aTotalTransform,
619 aPolygonDeviceRectOrig,
620 nStepCount,
621 false );
622 rOutDev.Pop();
624 if( p2ndOutDev && nTransparency < 253 )
626 // HACK. Normally, CanvasHelper does not care about
627 // actually what mp2ndOutDev is... well, here we do &
628 // assume a 1bpp target - everything beyond 97%
629 // transparency is fully transparent
630 p2ndOutDev->SetFillColor( COL_BLACK );
631 p2ndOutDev->DrawPolyPolygon( rPoly );
635 #if OSL_DEBUG_LEVEL > 3
636 // extra-verbosity
638 ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
639 ::basegfx::B2DRectangle aTextureDeviceRect;
640 ::basegfx::B2DHomMatrix aTextureTransform;
641 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
642 aRect,
643 aTextureTransform );
644 rOutDev.SetLineColor( COL_RED );
645 rOutDev.SetFillColor();
646 rOutDev.DrawRect( vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
648 rOutDev.SetLineColor( COL_BLUE );
649 ::Polygon aPoly1(
650 vcl::unotools::rectangleFromB2DRectangle( aRect ));
651 ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() );
652 aPoly2.transform( aTextureTransform );
653 ::Polygon aPoly3( aPoly2 );
654 rOutDev.DrawPolygon( aPoly3 );
656 #endif
658 return true;
662 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* pCanvas,
663 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
664 const rendering::ViewState& viewState,
665 const rendering::RenderState& renderState,
666 const uno::Sequence< rendering::Texture >& textures )
668 ENSURE_ARG_OR_THROW( xPolyPolygon.is(),
669 "CanvasHelper::fillPolyPolygon(): polygon is NULL");
670 ENSURE_ARG_OR_THROW( textures.getLength(),
671 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");
673 if( mpOutDev )
675 tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
677 const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) );
678 ::tools::PolyPolygon aPolyPoly( tools::mapPolyPolygon(
679 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon),
680 viewState, renderState ) );
682 // TODO(F1): Multi-texturing
683 if( textures[0].Gradient.is() )
685 // try to cast XParametricPolyPolygon2D reference to
686 // our implementation class.
687 ::canvas::ParametricPolyPolygon* pGradient =
688 dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
690 if( pGradient && pGradient->getValues().maColors.getLength() )
692 // copy state from Gradient polypoly locally
693 // (given object might change!)
694 const ::canvas::ParametricPolyPolygon::Values& rValues(
695 pGradient->getValues() );
697 if( rValues.maColors.getLength() < 2 )
699 rendering::RenderState aTempState=renderState;
700 aTempState.DeviceColor = rValues.maColors[0];
701 fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState);
703 else
705 std::vector< ::Color > aColors(rValues.maColors.getLength());
706 std::transform(&rValues.maColors[0],
707 &rValues.maColors[0]+rValues.maColors.getLength(),
708 aColors.begin(),
709 boost::bind(
710 &vcl::unotools::stdColorSpaceSequenceToColor,
711 _1));
713 // TODO(E1): Return value
714 // TODO(F1): FillRule
715 gradientFill( mpOutDev->getOutDev(),
716 mp2ndOutDev.get() ? &mp2ndOutDev->getOutDev() : (OutputDevice*)NULL,
717 rValues,
718 aColors,
719 aPolyPoly,
720 viewState,
721 renderState,
722 textures[0],
723 nTransparency );
726 else
728 // TODO(F1): The generic case is missing here
729 ENSURE_OR_THROW( false,
730 "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
733 else if( textures[0].Bitmap.is() )
735 const geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() );
737 ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 &&
738 aBmpSize.Height != 0,
739 "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" );
741 // determine maximal bound rect of texture-filled
742 // polygon
743 const ::Rectangle aPolygonDeviceRect(
744 aPolyPoly.GetBoundRect() );
747 // first of all, determine whether we have a
748 // drawBitmap() in disguise
749 // =========================================
751 const bool bRectangularPolygon( tools::isRectangle( aPolyPoly ) );
753 ::basegfx::B2DHomMatrix aTotalTransform;
754 ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform,
755 viewState,
756 renderState);
757 ::basegfx::B2DHomMatrix aTextureTransform;
758 ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
759 textures[0].AffineTransform );
761 aTotalTransform *= aTextureTransform;
763 const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0);
764 ::basegfx::B2DRectangle aTextureDeviceRect;
765 ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect,
766 aRect,
767 aTotalTransform );
769 const ::Rectangle aIntegerTextureDeviceRect(
770 vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) );
772 if( bRectangularPolygon &&
773 aIntegerTextureDeviceRect == aPolygonDeviceRect )
775 rendering::RenderState aLocalState( renderState );
776 ::canvas::tools::appendToRenderState(aLocalState,
777 aTextureTransform);
778 ::basegfx::B2DHomMatrix aScaleCorrection;
779 aScaleCorrection.scale( 1.0/aBmpSize.Width,
780 1.0/aBmpSize.Height );
781 ::canvas::tools::appendToRenderState(aLocalState,
782 aScaleCorrection);
784 // need alpha modulation?
785 if( !::rtl::math::approxEqual( textures[0].Alpha,
786 1.0 ) )
788 // setup alpha modulation values
789 aLocalState.DeviceColor.realloc(4);
790 double* pColor = aLocalState.DeviceColor.getArray();
791 pColor[0] =
792 pColor[1] =
793 pColor[2] = 0.0;
794 pColor[3] = textures[0].Alpha;
796 return drawBitmapModulated( pCanvas,
797 textures[0].Bitmap,
798 viewState,
799 aLocalState );
801 else
803 return drawBitmap( pCanvas,
804 textures[0].Bitmap,
805 viewState,
806 aLocalState );
809 else
811 // No easy mapping to drawBitmap() - calculate
812 // texturing parameters
813 // ===========================================
815 BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) );
817 // scale down bitmap to [0,1]x[0,1] rect, as required
818 // from the XCanvas interface.
819 ::basegfx::B2DHomMatrix aScaling;
820 ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform
821 aScaling.scale( 1.0/aBmpSize.Width,
822 1.0/aBmpSize.Height );
824 aTotalTransform = aTextureTransform * aScaling;
825 aPureTotalTransform = aTextureTransform;
827 // combine with view and render transform
828 ::basegfx::B2DHomMatrix aMatrix;
829 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);
831 // combine all three transformations into one
832 // global texture-to-device-space transformation
833 aTotalTransform *= aMatrix;
834 aPureTotalTransform *= aMatrix;
836 // analyze transformation, and setup an
837 // appropriate GraphicObject
838 ::basegfx::B2DVector aScale;
839 ::basegfx::B2DPoint aOutputPos;
840 double nRotate;
841 double nShearX;
842 aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX );
844 GraphicAttr aGrfAttr;
845 GraphicObjectSharedPtr pGrfObj;
847 if( ::basegfx::fTools::equalZero( nShearX ) )
849 // no shear, GraphicObject is enough (the
850 // GraphicObject only supports scaling, rotation
851 // and translation)
853 // setup GraphicAttr
854 aGrfAttr.SetMirrorFlags(
855 ( aScale.getX() < 0.0 ? BmpMirrorFlags::Horizontal : BmpMirrorFlags::NONE ) |
856 ( aScale.getY() < 0.0 ? BmpMirrorFlags::Vertical : BmpMirrorFlags::NONE ) );
857 aGrfAttr.SetRotation( static_cast< sal_uInt16 >(::basegfx::fround( nRotate*10.0 )) );
859 pGrfObj.reset( new GraphicObject( aBmpEx ) );
861 else
863 // complex transformation, use generic affine bitmap
864 // transformation
865 aBmpEx = tools::transformBitmap( aBmpEx,
866 aTotalTransform,
867 uno::Sequence< double >(),
868 tools::MODULATE_NONE);
870 pGrfObj.reset( new GraphicObject( aBmpEx ) );
872 // clear scale values, generated bitmap already
873 // contains scaling
874 aScale.setX( 0.0 ); aScale.setY( 0.0 );
878 // render texture tiled into polygon
879 // =================================
881 // calc device space direction vectors. We employ
882 // the followin approach for tiled output: the
883 // texture bitmap is output in texture space
884 // x-major order, i.e. tile neighbors in texture
885 // space x direction are rendered back-to-back in
886 // device coordinate space (after the full device
887 // transformation). Thus, the aNextTile* vectors
888 // denote the output position updates in device
889 // space, to get from one tile to the next.
890 ::basegfx::B2DVector aNextTileX( 1.0, 0.0 );
891 ::basegfx::B2DVector aNextTileY( 0.0, 1.0 );
892 aNextTileX *= aPureTotalTransform;
893 aNextTileY *= aPureTotalTransform;
895 ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform );
897 ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(),
898 "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" );
900 aInverseTextureTransform.invert();
902 // calc bound rect of extended texture area in
903 // device coordinates. Therefore, we first calc
904 // the area of the polygon bound rect in texture
905 // space. To maintain texture phase, this bound
906 // rect is then extended to integer coordinates
907 // (extended, because shrinking might leave some
908 // inner polygon areas unfilled).
909 // Finally, the bound rect is transformed back to
910 // device coordinate space, were we determine the
911 // start point from it.
912 ::basegfx::B2DRectangle aTextureSpacePolygonRect;
913 ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect,
914 vcl::unotools::b2DRectangleFromRectangle(
915 aPolygonDeviceRect ),
916 aInverseTextureTransform );
918 // calc left, top of extended polygon rect in
919 // texture space, create one-texture instance rect
920 // from it (i.e. rect from start point extending
921 // 1.0 units to the right and 1.0 units to the
922 // bottom). Note that the rounding employed here
923 // is a bit subtle, since we need to round up/down
924 // as _soon_ as any fractional amount is
925 // encountered. This is to ensure that the full
926 // polygon area is filled with texture tiles.
927 const sal_Int32 nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) );
928 const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) );
929 const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) );
930 const sal_Int32 nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxY() ) );
931 const ::basegfx::B2DRectangle aSingleTextureRect(
932 nX1, nY1,
933 nX1 + 1.0,
934 nY1 + 1.0 );
936 // and convert back to device space
937 ::basegfx::B2DRectangle aSingleDeviceTextureRect;
938 ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect,
939 aSingleTextureRect,
940 aPureTotalTransform );
942 const ::Point aPtRepeat( vcl::unotools::pointFromB2DPoint(
943 aSingleDeviceTextureRect.getMinimum() ) );
944 const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ),
945 ::basegfx::fround( aScale.getY() * aBmpSize.Height ) );
946 const ::Size aIntegerNextTileX( vcl::unotools::sizeFromB2DSize(aNextTileX) );
947 const ::Size aIntegerNextTileY( vcl::unotools::sizeFromB2DSize(aNextTileY) );
949 const ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
950 ::basegfx::fround( aOutputPos.getX() ) : aPtRepeat.X(),
951 textures[0].RepeatModeY == rendering::TexturingMode::NONE ?
952 ::basegfx::fround( aOutputPos.getY() ) : aPtRepeat.Y() );
953 const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
954 1 : nX2 - nX1 );
955 const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ?
956 1 : nY2 - nY1 );
958 OutputDevice& rOutDev( mpOutDev->getOutDev() );
960 if( bRectangularPolygon )
962 // use optimized output path
965 // this distinction really looks like a
966 // micro-optimization, but in fact greatly speeds up
967 // especially complex fills. That's because when using
968 // clipping, we can output polygons instead of
969 // poly-polygons, and don't have to output the gradient
970 // twice for XOR
972 // setup alpha modulation
973 if( !::rtl::math::approxEqual( textures[0].Alpha,
974 1.0 ) )
976 // TODO(F1): Note that the GraphicManager has
977 // a subtle difference in how it calculates
978 // the resulting alpha value: it's using the
979 // inverse alpha values (i.e. 'transparency'),
980 // and calculates transOrig + transModulate,
981 // instead of transOrig + transModulate -
982 // transOrig*transModulate (which would be
983 // equivalent to the origAlpha*modulateAlpha
984 // the DX canvas performs)
985 aGrfAttr.SetTransparency(
986 static_cast< sal_uInt8 >(
987 ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
990 rOutDev.IntersectClipRegion( aPolygonDeviceRect );
991 textureFill( rOutDev,
992 *pGrfObj,
993 aPt,
994 aIntegerNextTileX,
995 aIntegerNextTileY,
996 nTilesX,
997 nTilesY,
998 aSz,
999 aGrfAttr );
1001 if( mp2ndOutDev )
1003 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1004 r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect );
1005 textureFill( r2ndOutDev,
1006 *pGrfObj,
1007 aPt,
1008 aIntegerNextTileX,
1009 aIntegerNextTileY,
1010 nTilesX,
1011 nTilesY,
1012 aSz,
1013 aGrfAttr );
1016 else
1018 // output texture the hard way: XORing out the
1019 // polygon
1020 // ===========================================
1022 if( !::rtl::math::approxEqual( textures[0].Alpha,
1023 1.0 ) )
1025 // uh-oh. alpha blending is required,
1026 // cannot do direct XOR, but have to
1027 // prepare the filled polygon within a
1028 // VDev
1029 ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev );
1030 pVDev->SetOutputSizePixel( aPolygonDeviceRect.GetSize() );
1032 // shift output to origin of VDev
1033 const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() );
1034 aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(),
1035 -aPolygonDeviceRect.Top() ) );
1037 const vcl::Region aPolyClipRegion( aPolyPoly );
1039 pVDev->SetClipRegion( aPolyClipRegion );
1040 textureFill( *pVDev.get(),
1041 *pGrfObj,
1042 aOutPos,
1043 aIntegerNextTileX,
1044 aIntegerNextTileY,
1045 nTilesX,
1046 nTilesY,
1047 aSz,
1048 aGrfAttr );
1050 // output VDev content alpha-blended to
1051 // target position.
1052 const ::Point aEmptyPoint;
1053 Bitmap aContentBmp(
1054 pVDev->GetBitmap( aEmptyPoint,
1055 pVDev->GetOutputSizePixel() ) );
1057 sal_uInt8 nCol( static_cast< sal_uInt8 >(
1058 ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) );
1059 AlphaMask aAlpha( pVDev->GetOutputSizePixel(),
1060 &nCol );
1062 BitmapEx aOutputBmpEx( aContentBmp, aAlpha );
1063 rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1064 aOutputBmpEx );
1066 if( mp2ndOutDev )
1067 mp2ndOutDev->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(),
1068 aOutputBmpEx );
1070 else
1072 const vcl::Region aPolyClipRegion( aPolyPoly );
1074 rOutDev.Push( PushFlags::CLIPREGION );
1075 rOutDev.SetClipRegion( aPolyClipRegion );
1077 textureFill( rOutDev,
1078 *pGrfObj,
1079 aPt,
1080 aIntegerNextTileX,
1081 aIntegerNextTileY,
1082 nTilesX,
1083 nTilesY,
1084 aSz,
1085 aGrfAttr );
1086 rOutDev.Pop();
1088 if( mp2ndOutDev )
1090 OutputDevice& r2ndOutDev( mp2ndOutDev->getOutDev() );
1091 r2ndOutDev.Push( PushFlags::CLIPREGION );
1093 r2ndOutDev.SetClipRegion( aPolyClipRegion );
1094 textureFill( r2ndOutDev,
1095 *pGrfObj,
1096 aPt,
1097 aIntegerNextTileX,
1098 aIntegerNextTileY,
1099 nTilesX,
1100 nTilesY,
1101 aSz,
1102 aGrfAttr );
1103 r2ndOutDev.Pop();
1111 // TODO(P1): Provide caching here.
1112 return uno::Reference< rendering::XCachedPrimitive >(NULL);
1117 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */