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