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