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 #include <canvas/verbosetrace.hxx> 30 #include <canvas/canvastools.hxx> 31 32 #include <rtl/math.hxx> 33 34 #include <basegfx/matrix/b2dhommatrix.hxx> 35 #include <basegfx/point/b2dpoint.hxx> 36 #include <basegfx/tools/canvastools.hxx> 37 #include <basegfx/polygon/b2dpolygon.hxx> 38 #include <basegfx/polygon/b2dpolygontools.hxx> 39 #include <basegfx/polygon/b2dpolypolygontools.hxx> 40 #include <basegfx/numeric/ftools.hxx> 41 42 #include <canvas/base/canvascustomspritehelper.hxx> 43 44 using namespace ::com::sun::star; 45 46 47 namespace canvas 48 { 49 bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite ) 50 { 51 if( !mxClipPoly.is() ) 52 { 53 // empty clip polygon -> everything is visible now 54 maCurrClipBounds.reset(); 55 mbIsCurrClipRectangle = true; 56 } 57 else 58 { 59 const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() ); 60 61 // clip is not empty - determine actual update area 62 ::basegfx::B2DPolyPolygon aClipPath( 63 polyPolygonFromXPolyPolygon2D( mxClipPoly ) ); 64 65 // apply sprite transformation also to clip! 66 aClipPath.transform( maTransform ); 67 68 // clip which is about to be set, expressed as a 69 // b2drectangle 70 const ::basegfx::B2DRectangle& rClipBounds( 71 ::basegfx::tools::getRange( aClipPath ) ); 72 73 const ::basegfx::B2DRectangle aBounds( 0.0, 0.0, 74 maSize.getX(), 75 maSize.getY() ); 76 77 // rectangular area which is actually covered by the sprite. 78 // coordinates are relative to the sprite origin. 79 ::basegfx::B2DRectangle aSpriteRectPixel; 80 ::canvas::tools::calcTransformedRectBounds( aSpriteRectPixel, 81 aBounds, 82 maTransform ); 83 84 // aClipBoundsA = new clip bound rect, intersected 85 // with sprite area 86 ::basegfx::B2DRectangle aClipBoundsA(rClipBounds); 87 aClipBoundsA.intersect( aSpriteRectPixel ); 88 89 if( nNumClipPolygons != 1 ) 90 { 91 // clip cannot be a single rectangle -> cannot 92 // optimize update 93 mbIsCurrClipRectangle = false; 94 maCurrClipBounds = aClipBoundsA; 95 } 96 else 97 { 98 // new clip could be a single rectangle - check 99 // that now: 100 const bool bNewClipIsRect( 101 ::basegfx::tools::isRectangle( aClipPath.getB2DPolygon(0) ) ); 102 103 // both new and old clip are truly rectangles 104 // - can now take the optimized path 105 const bool bUseOptimizedUpdate( bNewClipIsRect && 106 mbIsCurrClipRectangle ); 107 108 const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds ); 109 110 // store new current clip type 111 maCurrClipBounds = aClipBoundsA; 112 mbIsCurrClipRectangle = bNewClipIsRect; 113 114 if( mbActive && 115 bUseOptimizedUpdate ) 116 { 117 // aClipBoundsB = maCurrClipBounds, i.e. last 118 // clip, intersected with sprite area 119 typedef ::std::vector< ::basegfx::B2DRectangle > VectorOfRects; 120 VectorOfRects aClipDifferences; 121 122 // get all rectangles covered by exactly one 123 // of the polygons (aka XOR) 124 ::basegfx::computeSetDifference(aClipDifferences, 125 aClipBoundsA, 126 aOldBounds); 127 128 // aClipDifferences now contains the final 129 // update areas, coordinates are still relative 130 // to the sprite origin. before submitting 131 // this area to 'updateSprite()' we need to 132 // translate this area to the final position, 133 // coordinates need to be relative to the 134 // spritecanvas. 135 VectorOfRects::const_iterator aCurr( aClipDifferences.begin() ); 136 const VectorOfRects::const_iterator aEnd( aClipDifferences.end() ); 137 while( aCurr != aEnd ) 138 { 139 mpSpriteCanvas->updateSprite( 140 rSprite, 141 maPosition, 142 ::basegfx::B2DRectangle( 143 maPosition + aCurr->getMinimum(), 144 maPosition + aCurr->getMaximum() ) ); 145 ++aCurr; 146 } 147 148 // update calls all done 149 return true; 150 } 151 } 152 } 153 154 // caller needs to perform update calls 155 return false; 156 } 157 158 CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() : 159 mpSpriteCanvas(), 160 maCurrClipBounds(), 161 maPosition(), 162 maSize(), 163 maTransform(), 164 mxClipPoly(), 165 mfPriority(0.0), 166 mfAlpha(0.0), 167 mbActive(false), 168 mbIsCurrClipRectangle(true), 169 mbIsContentFullyOpaque( false ), 170 mbAlphaDirty( true ), 171 mbPositionDirty( true ), 172 mbTransformDirty( true ), 173 mbClipDirty( true ), 174 mbPrioDirty( true ), 175 mbVisibilityDirty( true ) 176 { 177 } 178 179 void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& rSpriteSize, 180 const SpriteSurface::Reference& rOwningSpriteCanvas ) 181 { 182 ENSURE_OR_THROW( rOwningSpriteCanvas.get(), 183 "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" ); 184 185 mpSpriteCanvas = rOwningSpriteCanvas; 186 maSize.setX( ::std::max( 1.0, 187 ceil( rSpriteSize.Width ) ) ); // round up to nearest int, 188 // enforce sprite to have at 189 // least (1,1) pixel size 190 maSize.setY( ::std::max( 1.0, 191 ceil( rSpriteSize.Height ) ) ); 192 } 193 194 void CanvasCustomSpriteHelper::disposing() 195 { 196 mpSpriteCanvas.clear(); 197 } 198 199 void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ ) 200 { 201 // about to clear content to fully transparent 202 mbIsContentFullyOpaque = false; 203 } 204 205 void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference& rSprite, 206 const uno::Reference< rendering::XBitmap >& xBitmap, 207 const rendering::ViewState& viewState, 208 const rendering::RenderState& renderState ) 209 { 210 // check whether bitmap is non-alpha, and whether its 211 // transformed size covers the whole sprite. 212 if( !xBitmap->hasAlpha() ) 213 { 214 const geometry::IntegerSize2D& rInputSize( 215 xBitmap->getSize() ); 216 const ::basegfx::B2DSize& rOurSize( 217 rSprite->getSizePixel() ); 218 219 ::basegfx::B2DHomMatrix aTransform; 220 if( tools::isInside( 221 ::basegfx::B2DRectangle( 0.0,0.0, 222 rOurSize.getX(), 223 rOurSize.getY() ), 224 ::basegfx::B2DRectangle( 0.0,0.0, 225 rInputSize.Width, 226 rInputSize.Height ), 227 ::canvas::tools::mergeViewAndRenderTransform(aTransform, 228 viewState, 229 renderState) ) ) 230 { 231 // bitmap is opaque and will fully cover the sprite, 232 // set flag appropriately 233 mbIsContentFullyOpaque = true; 234 } 235 } 236 } 237 238 void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference& rSprite, 239 double alpha ) 240 { 241 if( !mpSpriteCanvas.get() ) 242 return; // we're disposed 243 244 if( alpha != mfAlpha ) 245 { 246 mfAlpha = alpha; 247 248 if( mbActive ) 249 { 250 mpSpriteCanvas->updateSprite( rSprite, 251 maPosition, 252 getUpdateArea() ); 253 } 254 255 mbAlphaDirty = true; 256 } 257 } 258 259 void CanvasCustomSpriteHelper::move( const Sprite::Reference& rSprite, 260 const geometry::RealPoint2D& aNewPos, 261 const rendering::ViewState& viewState, 262 const rendering::RenderState& renderState ) 263 { 264 if( !mpSpriteCanvas.get() ) 265 return; // we're disposed 266 267 ::basegfx::B2DHomMatrix aTransform; 268 ::canvas::tools::mergeViewAndRenderTransform(aTransform, 269 viewState, 270 renderState); 271 272 // convert position to device pixel 273 ::basegfx::B2DPoint aPoint( 274 ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) ); 275 aPoint *= aTransform; 276 277 if( aPoint != maPosition ) 278 { 279 const ::basegfx::B2DRectangle& rBounds( getFullSpriteRect() ); 280 281 if( mbActive ) 282 { 283 mpSpriteCanvas->moveSprite( rSprite, 284 rBounds.getMinimum(), 285 rBounds.getMinimum() - maPosition + aPoint, 286 rBounds.getRange() ); 287 } 288 289 maPosition = aPoint; 290 mbPositionDirty = true; 291 } 292 } 293 294 void CanvasCustomSpriteHelper::transform( const Sprite::Reference& rSprite, 295 const geometry::AffineMatrix2D& aTransformation ) 296 { 297 ::basegfx::B2DHomMatrix aMatrix; 298 ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix, 299 aTransformation); 300 301 if( maTransform != aMatrix ) 302 { 303 // retrieve bounds before and after transformation change. 304 const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() ); 305 306 maTransform = aMatrix; 307 308 if( !updateClipState( rSprite ) && 309 mbActive ) 310 { 311 mpSpriteCanvas->updateSprite( rSprite, 312 maPosition, 313 rPrevBounds ); 314 mpSpriteCanvas->updateSprite( rSprite, 315 maPosition, 316 getUpdateArea() ); 317 } 318 319 mbTransformDirty = true; 320 } 321 } 322 323 void CanvasCustomSpriteHelper::clip( const Sprite::Reference& rSprite, 324 const uno::Reference< rendering::XPolyPolygon2D >& xClip ) 325 { 326 // NULL xClip explicitely allowed here (to clear clipping) 327 328 // retrieve bounds before and after clip change. 329 const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() ); 330 331 mxClipPoly = xClip; 332 333 if( !updateClipState( rSprite ) && 334 mbActive ) 335 { 336 mpSpriteCanvas->updateSprite( rSprite, 337 maPosition, 338 rPrevBounds ); 339 mpSpriteCanvas->updateSprite( rSprite, 340 maPosition, 341 getUpdateArea() ); 342 } 343 344 mbClipDirty = true; 345 } 346 347 void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference& rSprite, 348 double nPriority ) 349 { 350 if( !mpSpriteCanvas.get() ) 351 return; // we're disposed 352 353 if( nPriority != mfPriority ) 354 { 355 mfPriority = nPriority; 356 357 if( mbActive ) 358 { 359 mpSpriteCanvas->updateSprite( rSprite, 360 maPosition, 361 getUpdateArea() ); 362 } 363 364 mbPrioDirty = true; 365 } 366 } 367 368 void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite ) 369 { 370 if( !mpSpriteCanvas.get() ) 371 return; // we're disposed 372 373 if( !mbActive ) 374 { 375 mpSpriteCanvas->showSprite( rSprite ); 376 mbActive = true; 377 378 // TODO(P1): if clip is the NULL clip (nothing visible), 379 // also save us the update call. 380 381 if( mfAlpha != 0.0 ) 382 { 383 mpSpriteCanvas->updateSprite( rSprite, 384 maPosition, 385 getUpdateArea() ); 386 } 387 388 mbVisibilityDirty = true; 389 } 390 } 391 392 void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite ) 393 { 394 if( !mpSpriteCanvas.get() ) 395 return; // we're disposed 396 397 if( mbActive ) 398 { 399 mpSpriteCanvas->hideSprite( rSprite ); 400 mbActive = false; 401 402 // TODO(P1): if clip is the NULL clip (nothing visible), 403 // also save us the update call. 404 405 if( mfAlpha != 0.0 ) 406 { 407 mpSpriteCanvas->updateSprite( rSprite, 408 maPosition, 409 getUpdateArea() ); 410 } 411 412 mbVisibilityDirty = true; 413 } 414 } 415 416 // Sprite interface 417 bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const 418 { 419 if( !mbIsCurrClipRectangle || 420 !mbIsContentFullyOpaque || 421 !::rtl::math::approxEqual(mfAlpha, 1.0) ) 422 { 423 // sprite either transparent, or clip rect does not 424 // represent exact bounds -> update might not be fully 425 // opaque 426 return false; 427 } 428 else 429 { 430 // make sure sprite rect fully covers update area - 431 // although the update area originates from the sprite, 432 // it's by no means guaranteed that it's limited to this 433 // sprite's update area - after all, other sprites might 434 // have been merged, or this sprite is moving. 435 return getUpdateArea().isInside( rUpdateArea ); 436 } 437 } 438 439 ::basegfx::B2DPoint CanvasCustomSpriteHelper::getPosPixel() const 440 { 441 return maPosition; 442 } 443 444 ::basegfx::B2DVector CanvasCustomSpriteHelper::getSizePixel() const 445 { 446 return maSize; 447 } 448 449 ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const 450 { 451 // Internal! Only call with locked object mutex! 452 ::basegfx::B2DHomMatrix aTransform( maTransform ); 453 aTransform.translate( maPosition.getX(), 454 maPosition.getY() ); 455 456 // transform bounds at origin, as the sprite transformation is 457 // formulated that way 458 ::basegfx::B2DRectangle aTransformedBounds; 459 return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds, 460 rBounds, 461 aTransform ); 462 } 463 464 ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const 465 { 466 // Internal! Only call with locked object mutex! 467 468 // return effective sprite rect, i.e. take active clip into 469 // account 470 if( maCurrClipBounds.isEmpty() ) 471 return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0, 472 maSize.getX(), 473 maSize.getY() ) ); 474 else 475 return ::basegfx::B2DRectangle( 476 maPosition + maCurrClipBounds.getMinimum(), 477 maPosition + maCurrClipBounds.getMaximum() ); 478 } 479 480 double CanvasCustomSpriteHelper::getPriority() const 481 { 482 return mfPriority; 483 } 484 485 ::basegfx::B2DRange CanvasCustomSpriteHelper::getFullSpriteRect() const 486 { 487 // Internal! Only call with locked object mutex! 488 return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0, 489 maSize.getX(), 490 maSize.getY() ) ); 491 } 492 } 493