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